Introducción

Por medio de la ciencia de datos nos ayudamos a transformar y a entender los datos para tomar decisiones. Antes de comenzar a realizar análisis de datos, debemos realizar una exploración de ellos, entender los datos, para generar hipótesis, probarlas y luego repetirlas o ponerlas en marcha.

Para realizar mejores análisis se recomienda el ciclo de vida del proceso de ciencia de datos en equipo (TDSP), el cual es un proceso para estructural los proyectos de ciencia de datos.

Este ciclo de vida está diseñado para proyecto de ciencia de datos va desde la adquisición de los datos y entendimiento del problema, hasta la puesta en producción. Estas aplicaciones implementan modelos de aprendizaje o inteligencia artificial de máquina para realizar un análisis predictivo/clasificación. Los proyectos de ciencia de datos exploratorios y los proyectos de análisis improvisados también se pueden beneficiar del uso de este proceso. Pero para esos proyectos, algunos de los pasos descritos a continuación pueden no ser necesarios.

Fases del ciclo de vida de TDSP

El ciclo de vida de TDSP se compone por cinco fases principales de forma iterativa (Figura 1):

  1. Entender el negocio

  2. Adquisición y comprensión de los datos

  3. Modelado

  4. Implementación

  5. Aceptación del cliente

Figura 1. Ciclo de vida de TDSP

Herramientas básicas de exploración de datos

La exploración de datos es el arte de entender los datos, generar hipótesis e investigar las características de los datos (transformar registros en información). El objetivo de la exploración de datos es extraer información y tomar mejores decisiones para el modelo que se va a desarrollar.

La visualización de datos, es una de las formas de exploración más utilizadas. Las visualizaciones por si solas no suelen ser suficientes, por lo que se vuelve necesario procesar y transformar los datos, por medio de friltrados, creación de nuevas variables y cálculos (ingeniería de características), por ejemplo.

Finalmente, la combinación de la visualización y la transformación de los datos con su curiosidad y análisis, nos llevará a la extracción de información resolución de preguntas a cerca de los datos.

Visualización de datos

“The simple graph has brought more information to the data analyst’s mind than any other device.” — John Tukey

Comencemos a utilizar R_Studio y algunas de las paqueterías que nos ayudarán a entender los datos. Para la visualización de datos utilizaremos la paquetería ggplot2, aclarando que no es la única herramienta para hacerlo. Aquí puedes descargar algunos trucos de ggplot (ggplot2 cheatsheets).

Figura 2. Cheat Sheet (hoja de trucos) de ggplot2

Una de las paqueterías principales que vamos a utilizar es tidyverse y la cual es indispensable para el funcionamiento de ggplot2. Ahora la pregunta es ¿qué es tidyverse,? a continuación hablaremos sobre esta paquetería.

¿Qué es tidyverse?

El tidyverse es un conjunto de paquetes en R diseñados para ciencia de datos. Todos los paquetes comparten una filosofía de diseño, una gramática y estructuras de datos subyacentes.

Todos estos paquetes nos ayudan a importar, ordenar, transformar y así entender los datos, para finalmente poder obtener y comunicar sus resultados de forma más fácil.

Figura 3. tidyverse

Se pueden instalar todos los paquetes desde tidyverse simplemente escribiendo en tu consola de R install.packages("tidyverse").

# Sólo se necesita instalar la primera vez este paquete en tu computadora
install.packages("tidyverse")

Para que funcione este paquete, siempre en nuestro proyecto debemos de cargar su librería corriendo el siguiente código:

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.2     ✓ dplyr   1.0.7
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()

En el código, si se necesita ser explicito de una función o dataset de un paquete en particular, podemos usar la función form_package::function(). Por ejemplo, ggplot2::ggplot() dice que, explícitamente, se quiere usar la función ggplot() del paquete ggplot2.

Hay muchos paquetes que usaremos que cumplen con la estructura de los datos y las práticas de tidyverse que deben instalarse de forma independiente usando el comando install.packages("package") y luego cargar su librería. Algunos de los que usaremos será lubridate y tsibble, que son paquetes especiales para fechas.

El comando %>% que se conoce como pipe o en español tubería, es un comando que sólo funciona con la librería de tidyverse. Se utiliza para enfatizar una secuencia de acciones comenzando con un conjunto de datos. Debe de estar antecedido de una línea y es más fácil de entender si se hacen acciones por líneas. Se puede leer este comando en el código como si fuera un luego, qué es lo que sigue, en la ejecución y realizar en una sola línea varias acciones.

Para los ejemplo usaremos una base de datos de créditos de Alemania, los cuales se extrageron de kaggle.

Un resumen de la descripción de las categorías se presenta a continuación: - Edad (numérico) - Sexo (texto: masculino, femenino) - Trabajo (numérico: 0 - no cualificado y no residente, 1 - no cualificado y residente, 2 - cualificado, 3 - muy cualificado) - Vivienda (texto: propia, de alquiler o gratuita) - Cuentas de ahorro (texto: poco, moderado, bastante, mucho) - Cuenta corriente (numérico, en DM - marco alemán) - Importe del crédito (numérico, en DM) - Duración (numérico, en meses) - Finalidad (texto: coche, muebles/equipamiento, radio/TV, electrodomésticos, reparaciones, educación, negocios, vacaciones/otros)

Traducción realizada con la versión gratuita del traductor www.DeepL.com/Translator

data <- read.csv("https://datahub.io/machine-learning/credit-g/r/credit-g.csv")
head(data)

Podemos interpretar el pipe con la palabra luego. Por ejemplo, tomando los datos de crédito anterior y queremos un filtro de la varibale housing por own, usando R base y luego usando tidiverse con el comando %>%:

# R base
head(filter(data,housing == 'own'))
# Tidiverse con el pipe %>% 
data %>% filter(housing == 'own') %>% head()

En la última línea obtenemos el mismo resultado que si lo hacemos con R base, solo que podemos ir interpretando más fácilmente el código. Este se puede leer como toma data y luego aplica un filtro a la variable Sex = 'male' y luego muestra los primeros 6 registros (encabezado).

Un ejemplo de utilización del pipe se puede ser:

data %>% group_by(class,personal_status) %>% 
  summarise(n= n(), avg_existing_credits = mean(existing_credits)) %>% 
  arrange(personal_status,-n)
`summarise()` has grouped output by 'class'. You can override using the `.groups` argument.

Visualización de datos con ggplot2

Para ejemplificar la visualización en R analizaremos el conjunto de datos (variables en las columnas y observaciones en las filas) que contiene datos recopilados de la Agencia de Protección Ambiental de Estados Unidos para 38 modelos de autos. Este data frame se encuentra en R con la función mpg, el nombre de este data frame viene de la abreviación de (miles per gallon o en español, millas por galón (mpg)). Para tener mayor información sobre mpg, pueden abrir ayuda y buscar esta función o en su consola correr el comando ?mpg.

Para mirar cuáles son las variables (nombre las columnas) lo podemos hacer con la función colnames() y para sacar un resumen de nuestros datos utilizamos la función summary().

colnames(mpg)
 [1] "manufacturer" "model"        "displ"        "year"         "cyl"          "trans"        "drv"          "cty"          "hwy"         
[10] "fl"           "class"       
summary(mpg)
 manufacturer          model               displ            year           cyl           trans               drv                 cty       
 Length:234         Length:234         Min.   :1.600   Min.   :1999   Min.   :4.000   Length:234         Length:234         Min.   : 9.00  
 Class :character   Class :character   1st Qu.:2.400   1st Qu.:1999   1st Qu.:4.000   Class :character   Class :character   1st Qu.:14.00  
 Mode  :character   Mode  :character   Median :3.300   Median :2004   Median :6.000   Mode  :character   Mode  :character   Median :17.00  
                                       Mean   :3.472   Mean   :2004   Mean   :5.889                                         Mean   :16.86  
                                       3rd Qu.:4.600   3rd Qu.:2008   3rd Qu.:8.000                                         3rd Qu.:19.00  
                                       Max.   :7.000   Max.   :2008   Max.   :8.000                                         Max.   :35.00  
      hwy             fl               class          
 Min.   :12.00   Length:234         Length:234        
 1st Qu.:18.00   Class :character   Class :character  
 Median :24.00   Mode  :character   Mode  :character  
 Mean   :23.44                                        
 3rd Qu.:27.00                                        
 Max.   :44.00                                        

Queremos responder la pregunta: ¿Los autos con motor más grandes usan más combustible que los autos con motor más pequeño.? Probablemente ya tenga una respuesta, pero, intentemos que su respuesta sea precisa. ¿Cómo se ve la relación entre el tamaño del motor y la eficiencia del combustible? ¿Es positivo? ¿es negativo? ¿es lineal? ¿es no lineal.?

Hay dos variables en nuestro data frame de mpg que nos podrían ayudar a responder:

  1. displ indica el tamaño del motor en litros

  2. hwy indica la eficiencia de combustible del auto en carretera en millas por galón (mpg)

Para realizar el gráfico vamos a poner la variable del tamaño del motor (displ) en el eje x y la eficiencia (hmy) en el eje y y utilizaremos la paquetería ggplot2.

# Cargar la paquetería de ggplot2
library("ggplot2")

# Grafico
mpg %>%  ggplot(aes(x = displ, y = hwy)) 

En el comando anterior primero estamos tomando los datos mpg, luego %>% tomamos la función ggplot() y le estamos especificando los ejes con la función aes(), pero aún no le hemos especificado qué tipo de gráfico será, es por ello que no nos muestra ninguna visualización.

Si por ejemplo queremos que cada dato lo muestre como un punto, vamos a adicionar al ggplot con un más (+) la función geom_point(). Para ver otros tipos de gráficos nos podemos apoyar de la hoja de trucos de ggplot2 presentada en la sesión de introducción.

# Grafico
mpg %>%  ggplot(aes(x = displ, y = hwy)) + 
  geom_point()

Por lo tanto, a continuación mostramos de forma generar los pasos para describir cómo funciona un gráfico ggplot2:

  1. Comencemos con un objeto ggplot (), donde especifique los datos que se utilizarán,

  2. proporcionar el mapeo estético (con aes ()),

  3. agreguemos capas:

    • Si desea un diagrama de dispersión, use geom_point (), histograma geom_hist (). Otros gráficos comunes son geom_line (), geom_bar (), geom_boxplot ().

    • definir escalas de color, como scale_color_brewer () o scale_color_distiller (),

    • especificaciones de facetas facet_wrap () o facet_grid ()

    • sistemas de coordenadas, como coord_cartesian (), coord_flip ()

Cada elemento está separado con un signo más (** + **):

ggplot(mpg, aes(displ, hwy, colour = class)) +
  geom_point() +
  facet_wrap(~manufacturer)

Debemos tener en cuenta que ggplot2 trabaja por capas, si deseamos que todo el gráfico tenga algo debemos de hacerlo desde la función primaria ggplot(), de lo contrario lo podemos hacer en las capas subyacentes. Para entender esta diferencia veamos las dos siguientes visualizaciones:

# El color aplica a todo el gráfico porque está en la función principal ggplot()
ggplot(mpg, aes(displ, hwy,color = class)) +
  geom_point() +
  geom_smooth()


# El color sólo aplica a los puntos y no toma en cuenta geom_smooth()
ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class)) +
  geom_smooth()

Análisis exploratorio de datos

La exploración de los datos nos puede proporcionar información dinámica, encontrar patrones y entender la historia de los datos. Es importante, antes de comenzar a entrar en el modelo, realizar un Análisis Exploratorio de los datos o en inglés Exploratory Data Analysis (EDA).

Para realizar este análisis, debemos hacer hipótesis respecto a los datos, transformar e inspeccionar de forma visual las propiedades estadísticas de los datos.

  • Cuál es el comportamiento en el tiempo de la variable (si aplica),

  • cuál es la variación de la variable,

  • identificación de valores atípicos,

  • distribuciones y variables estadísticas.

Algunos ejemplos:

Para comparar variables categóricas podemos usar diagramas de barras (geom_var()):

diamonds %>%  ggplot() +
  geom_bar(mapping = aes(x = cut)) +
  ggtitle("Count of Diamonds by cut quality")

Otra opción para entender las variables categóricas es hacer diagramas de cajas/bigotes o boxplots:

mpg  %>% ggplot() +
  geom_boxplot(mapping = aes(x = reorder(class, hwy, FUN = median), y = hwy))+ labs(x = "class", y = "hwy mpg")

Para variables continuas, podemos utilizar un histograma de frecuencias, especificando el tamaño de la clase con binwidth:

diamonds %>% ggplot() +
  geom_histogram(mapping = aes(x = carat), binwidth = 0.5) +
  ggtitle("Histogram of carats")

Si queremos visualizar histogramas de múltiples variables en un sólo gráfico, se puede usar la función geom_freqpoly() o usar facetas dentro de ggplot2``:

ggplot(data = diamonds %>% filter(carat < 3), mapping = aes(x = carat, colour = cut)) +
  geom_freqpoly(binwidth = 0.1)

ggplot(data = diamonds %>% filter(carat < 3), mapping = aes(x = carat, fill = cut)) +
  geom_histogram(binwidth = 0.1) +
  facet_wrap(~ cut) +
  theme(legend.position = "none")

library(gridExtra)

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
library(grid)

g <- ggplot(data = diamonds %>% filter(carat < 3), mapping = aes(x = carat))
g0 <- g + geom_histogram(binwidth = 0.5) +
  ggtitle("Binwidth = 0.5")
g1 <- g + geom_histogram(binwidth = 0.1) +
  ggtitle("Binwidth = 0.1")
g2 <- g + geom_histogram(binwidth = 0.01) +
  ggtitle("Binwidth = 0.01")

grid.arrange(g0,g1,g2, 
             top = "Histograms varying the binwidth",
             bottom = textGrob(
             "Different patterns can arise when selecting different binwidths",
             gp = gpar(fontface = 3, fontsize = 9),
             hjust = 1,
             x = 1))

Los anteriores son sólo algunos ejemplos, pero podría realizar muchísimas más visualizaciones como scatterplots, por ejemplo.

Reporte de exploración de datos

También podemos realizar reportes completos con DataExplorer::create_report(). Para el siguiente ejemplo debemos instalar los paquetes DataExplorer y nycflights13 para los datos (install.packages("nycflights13")).

library(nycflights13)
library(DataExplorer)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

En el paquete library(DataExplorer) hay 5 data frames:

  • airlines

  • airports

  • flights

  • planes

  • weather

Podemos visualizar su estructura de la siguiente forma:

data <- list(airlines, airports, flights, planes, weather)
plot_str(data)

Para tener un sólo dataset más robusto podemos fusionar las tablas por medio de la función merge()

final_data <- flights %>% merge(airlines, by= "carrier", all.x = TRUE) %>% 
  merge(planes, by = "tailnum", all.x = TRUE, suffixes = c("_flights", "_planes")) %>% 
  merge(airports, by.x = "origin", by.y = "faa", all.x = TRUE, suffixes = c("_carrier", "_origin")) %>% 
  merge(airports, by.x = "dest", by.y = "faa", all.x = TRUE, suffixes = c("_origin", "_dest"))

Análisis exploratorio de datos con la paquetería DataExplorer

Para conocer el conjunto de datos podemos realizar un summary() como lo hicimos en la sesión anterior o podemos utilizar la función introduce().

introduce(final_data)

# De forma gráfica
plot_intro(final_data)

Debemos de notar algo en este conjunto de datos:

  • Sólo el 0.27% de las filas están completas,

  • tenemos 5.7% de observaciones faltantes, es decir, dado que solo tenemos 0.27% de las filas completas, solo hay 5.7% de observaciones faltantes del total.

Estos valores faltantes nos podrán general problemas para analizar los datos, veamos un poco los perfiles que faltan.

Valores faltantes (missing values)

Siempre, en todos los problemas reales, vamos a tener datos desordenados y con problemas. Para visualizar el perfil de los datos faltantes podemos utilizar la función plot_missing().

plot_missing(final_data)

Si le gusta más tener la información en forma de tabla puede obtener su perfil por medio de la función profile_missing(final_data).

En la visualización anterior, podemos ver que la variable speed es la que en su mayoría le falta información, al parecer encontramos el culpable de que sólo el 0.27% de nuestras filas estén completas y probablemente esta variable no sea de mucha información. Por tanto la podemos eliminar de nuestro dataframe.

final_data <- drop_columns(final_data, "speed")

Distribuciones

La visualización de las distribuciones de frecuencia para todas las características discretas se puede realizar con la función plot_bar().

plot_bar(final_data)
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories

Tras una inspección detallada de la variable manuracturer, no es fácil identificar las siguientes características duplicadas:

  • AIRBUS and AIRBUS INDUSTRIE

  • CANADAIR and CANADAIR LTD

  • MCDONNELL DOUGLAS, MCDONNELL DOUGLAS AIRCRAFT CO and MCDONNELL DOUGLAS CORPORATION

Por tanto, debemos proceder a limpiar esta variable

final_data[which(final_data$manufacturer == "AIRBUS INDUSTRIE"),]$manufacturer <- "AIRBUS"
final_data[which(final_data$manufacturer == "CANADAIR LTD"),]$manufacturer <- "CANADAIR"
final_data[which(final_data$manufacturer %in% c("MCDONNELL DOUGLAS AIRCRAFT CO", "MCDONNELL DOUGLAS CORPORATION")),]$manufacturer <- "MCDONNELL DOUGLAS"

plot_bar(final_data$manufacturer)

Adicionalmente, las variables dst_origin, tzone_origin, year_flights y tz_origin contienen un solo valor, por lo que deberíamos proceder a eliminarla, ya que no nos proporciona información:

final_data <- drop_columns(final_data, c("dst_origin", "tzone_origin", "year_flights", "tz_origin"))

Con frecuencia, es muy beneficioso observar la distribución de frecuencia bivariada. Por ejemplo, para ver características discretas por arr_delay:

plot_bar(final_data, with = "arr_delay")
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories

Nótese que la distribución resultante se ve bastante diferente de la distribución de frecuencias regular.

Puede optar por dividir por colores todas las frecuencias por una variable discreta, como por ejemplo origin:

plot_bar(final_data, by = "origin")
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories

Histogramas

Para visualizar distribuciones de todas las variables continuas podemos utilizar la función plot_histogram():

plot_histogram(final_data)

También podemos observar que hay variables de fechas y horas que deben tratarse un poco más, por ejemplo, concentrando año, mes, día para formar una variable de fecha y/o agregar hora y minuto para formar la variable fecha_hora.

Otra parte que podemos limpiar, es por ejemplo la variable flight, ya que esa deberíamos tenerla como un factor, por ser un número de vuelo y numéricamente no tiene ningún significado:

final_data <- update_columns(final_data, "flight", as.factor)

También podemos remover las variables year_flights y tz_origin ya que sólo contienen un valor:

final_data <- drop_columns(final_data, c("year_flights", "tz_origin"))
Warning: Column 'year_flights' does not exist to removeWarning: Column 'tz_origin' does not exist to remove

QQ plot

La gráfica Quantile-Quantile es una forma de visualizar la desviasión de una distribución de probabilidad específica.

Después de analizar estos gráficos, a menudo es beneficioso aplicar una transformación matemática (como logaritmo) para modelos como la regresión lineal. Para hacerlo, podemos usar la función plot_qq. De forma predeterminada, se compara con la distribución normal.

Nota: La función llevará mucho tiempo con muchas observaciones, por lo que puede optar por especificar un sampled_rows apropiado:

qq_data <- final_data[, c("arr_delay", "air_time", "distance", "seats")]

plot_qq(qq_data, sampled_rows = 1000L)

En el gráfico, air_time, distance y asientos parecen sesgados en ambas colas. Apliquemos una transformación logarítmica simple y grafiquemos de nuevo.

log_qq_data <- update_columns(qq_data, 2:4, function(x) log(x + 1))

plot_qq(log_qq_data[, 2:4], sampled_rows = 1000L)

Con esto obtener una mejor distribución. Si es necesario, también puede ver el gráfico QQ mediante otra función:

qq_data <- final_data[, c("name_origin", "arr_delay", "air_time", "distance", "seats")]

plot_qq(qq_data, by = "name_origin", sampled_rows = 1000L)

Correlation Analysis

Para visualizar el mapa de calor de la correlación de todas las variables que no tengan datos faltantes lo podemos realizar de la siguiente forma:

plot_correlation(na.omit(final_data), maxcat = 5L)
11 features with more than 5 categories ignored!
dest: 100 categories
tailnum: 3246 categories
carrier: 16 categories
flight: 3773 categories
time_hour: 6642 categories
name_carrier: 16 categories
manufacturer: 24 categories
model: 121 categories
engine: 6 categories
name: 100 categories
tzone_dest: 7 categories

También puede graficar variables sólo discretas/continuas de la siguiente forma:

plot_correlation(na.omit(final_data), type = "c")

plot_correlation(na.omit(final_data), type = "d")
7 features with more than 20 categories ignored!
dest: 100 categories
tailnum: 3246 categories
flight: 3773 categories
time_hour: 6642 categories
manufacturer: 24 categories
model: 121 categories
name: 100 categories

Principal Component Analysis (PCA)

El análisis de componentes principales (PCA, por sus siglas en inglés,) consiste en expresar un conjunto de variables en un conjunto de combinaciones lineales de factores no correlacionados entre sí, estos factores dando cuenta una fracción cada vez más débil de la variabilidad de los datos.

Este análisis lo podemos realizar directamente con la función plot_prcomp (na.omit (final_data)), pero PCA funcionará mejor si limpiamos los datos primero:

pca_df <- na.omit(final_data[, c("origin", "dep_delay", "arr_delay", "air_time", "year_planes", "seats")])

plot_prcomp(pca_df, variance_cap = 0.9, nrow = 2L, ncol = 2L)

Boxplots

Suponga que le gustaría construir un modelo para predecir los retrasos en las llegadas, puede visualizar la distribución de todas las características continuas en función de los retrasos en las llegadas con un diagrama de caja/bigotes (boxplot):

## Reduce data size for demo purpose
arr_delay_df <- final_data[, c("arr_delay", "month", "day", "hour", "minute", "dep_delay", "distance", "year_planes", "seats")]

## Call boxplot function
plot_boxplot(arr_delay_df, by = "arr_delay")

Entre todos los cambios sutiles en correlación con los retrasos en las llegadas, se puede detectar inmediatamente que los aviones con más de 300 asientos tienden a tener retrasos mucho más largos (16 ~ 21 horas). Ahora podemos profundizar más para verificar o generar más hipótesis.

Scatterplots

Para mirar las relaciones entre variables podemos visualizar scatterplots o diagramas de dispersión.

arr_delay_df2 <- final_data[, c("arr_delay", "dep_time", "dep_delay", "arr_time", "air_time", "distance", "year_planes", "seats")]

plot_scatterplot(arr_delay_df2, by = "arr_delay", sampled_rows = 1000L)


Para comunicar los resultados

El último paso del flujo es comunicar los resultados. Este puede tener diferentes caminos, dependiendo del público.

  • Si desea presentar los resultados a su empresa y producir una herramienta reproducible que pueda utilizarse en la producción, puede hacer un Shiny app.

Documentos de R Markdown (como este documento) son formas de realizar informes, donde se puede combinar código, visualizaciones y descripciones (texto). Se puede realizar documentos, presentaciones, htmls, entre otros. Este tiene la ventaja que podemos escribir ecuaciones como en \(\LaTeX\), como por ejemplo:

\[ \int_{0}^{\infty} e^{-s \cdot t} f(t) d t=\lim _{h \rightarrow \infty} \int_{0}^{h} e^{-s, t} f(t) d t \]

  • Si desea publicar sus hallazgos para la comunidad científica, puede escribir un artículo.

  • También puede publicarlo en línea para que cualquiera pueda acceder a él.

Programar

La idea con esta sesión es que aprendes, recuerdes o mejores tus habilidades de programación en R. La programación es una habilidad transversal para todo científico de datos.

El código también lo podemos ver como una herramienta de comunicación con otras personas, adicionalmente que es la forma de decirle a tu computadora que debe de hacer. Pensar en código como un medio de comunicación nos puede ayudar a trabajar mejor en proyectos colaborativos.

Incluso si no trabajas con personas, puede que tu mismo necesites tu código hacia el futuro y es mejor construir tu código de tal forma que, si otra persona, o tu en el futuro lo requieras volver a ver, sea fácil y rápido de entender.

Si es la primera vez que usas R te recomiendo que estudies un poco más de lo adicional a las clases, el libro Advanced R by Hadley Wickham, te puede ayudar y dar mayores ideas de cómo programar en R.

Pipes

Los pipes son una herramienta para escribir código secuencial de múltiples operaciones. El pipe %>% proviene del paquete magrittr, pero el paquete de tidyverse cargan el %>% de forma automática, por lo que no es necesario que tengamos el paquete magrittr especificado explícitamente.

El objetivo de los pipes es ayudarte de escribir código de tal forma que sea más fácil de leer y de entender.

Un ejemplo es transformar un dataframe por medio de filtros, agrupaciones, etc. Esta forma de transformar los datos lo hacemos con la ayuda del paquete dplyr que ya viene dentro de tidyverse. Para ver todas las funciones que tiene este paquete para transformación de datos aquí te dejo un link que te puede ayudar.

# dataframe original
head(mpg)

# data frame transformado con pipe
mpg %>%    filter(manufacturer == 'audi') %>% 
  group_by(year) %>%  summarise(cty_mean = mean(cty),
                                hwy_mean = mean(hwy)) 

Funciones

Las funciones nos permiten automatizar tareas comunes de manera más potente y general que copiar y pegar. Escribir una función tiene tres ventajas principales:

  1. Puede dar a una función un nombre que te de idea de qué hace el código.

  2. Cuando se requiera cambiar, sólo se cambia una vez, en lugar de que si tenemos el mismo código repetido, tenemos que cambiarlo las veces que lo tengamos repetido.

  3. Se elimina la posibilidad de cometer errores.

Deberías considerar escribir una función siempre que hayas copiado y pegado un cloque de código más de dos veces. Veamos un ejemplo:

df <- tibble::tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)

df$a <- (df$a - min(df$a, na.rm = TRUE)) / 
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) / 
  (max(df$b, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) / 
  (max(df$c, na.rm = TRUE) - min(df$c, na.rm = TRUE))
df$d <- (df$d - min(df$d, na.rm = TRUE)) / 
  (max(df$d, na.rm = TRUE) - min(df$d, na.rm = TRUE))

Este código lo que hace es que reescala cada columna del dataframe df para que tenga un valor entre 0 y 1. Pero, hay un error en el código cuando se copió y pegó en df$b, en el momento de copiar y pegar hay una a en una parte del código en lugar de una b. ¿Lo viste?.

Para escribir una función hay que analizar primero el código. ¿Cuántas entradas tiene,? veamos la parte que se repite:

(df$a - min(df$a, na.rm = TRUE)) /
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
 [1] 0.6534246 1.0000000 0.6283353 0.6692940 0.8508011 0.7059234 0.8038226 0.2597706 0.6712101 0.0000000

Este código, la parte que vamos a cambiar es df$a, es decir sólo tiene una variable de entrada si lo vemos como una función. Lo que estamos haciendo en el código con el máximo y el mínimo en realidad es mirar su rango, podemos utilizar función range(), para su cálculo de tal forma que, la primera entrada es el mínimo y la segunda el máximo, así, la función que crearemos será:

# Función
rescale01 <- function(x) {
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}

#Evaluación de la función
rescale01(c(0, 5, 10))
[1] 0.0 0.5 1.0

Hay tres pasos claves para crear una función:

  1. Elegir correctamente el nombre de la función.

  2. Enumerar las entradas o argumentos de la función.

  3. Que tu código dentro de la función dependa de las variables de entrada.

Con esta función, se resuelve el problema original donde teníamos el error nos quedaría de la siguiente forma:

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)

Iteraciones

Una de las herramientas para ayudarnos a evitar duplicar código son las iteraciones, aunque debemos de tener cuidado con ellas y no darles un mal uso.

Volviendo al ejemplo anterior

df <- tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)

queremos calcular la media por columna, podríamos copiar y pegar realizarlo:

median(df$a)
[1] -0.8089347
median(df$b)
[1] -0.5162528
median(df$c)
[1] -0.02820393
median(df$d)
[1] -0.4217826

Una alternativa para no copiar y pegar es hacer un bucle:

output <- vector("double", ncol(df))  # 1. output
for (i in seq_along(df)) {            # 2. sequence
  output[[i]] <- median(df[[i]])      # 3. body
}
output
[1] -0.80893474 -0.51625280 -0.02820393 -0.42178257

Par los bucles o loops tenemos tres componente:

  1. La salida output <- vector("double", length(x))

  2. na forma general de crear un vector vacío de una longitud determinada es la función vector()

  3. La secuencia: i en seq_along(df). Esto determina lo que se va a recorrer en el bucle: cada ejecución del bucle for asignará a i un valor diferente de seq_along(df).

Práctica No. 1

Exploración y limpieza de datos para los siguientes dataframe:

  1. Coffee ratings conjunto de datos que mide la calificación del café según sus características. Variable respuesta: cupper_points.

  2. German Credit Ristk que contiene variables de crédito para realizar un score de probabilidad (si la persona paga o no paga) según sus características. Variable respuesta: Risk.

Para cada conjunto de datos van a realizar un Rmarkdown, donde se presente la exploración y limpieza de los datos. Si usted considera que se debe de adicionar una variable o modificar el dataset con agrupaciones o filtros por favor especificarlo en el reporte.

Antes de comenzar con el reporte hacerse una pregunta sobre los datos y responder a ella por medio de una o más visualizaciones. Al final del reporte colocar sus principales hallazgos y conclusiones.

NOTA: No se trata de hacer visualizaciones por hacerlas, cada una debe de ir acompañada de un análisis respecto a la variable respuesta para cada modelo.

LS0tCnRpdGxlOiAnQmxvcXVlIDE6IFByb2Nlc2FtaWVudG8geSBhbsOhbGlzaXMgZGUgZGF0b3MgZW4gUicKYXV0aG9yOiAnUHJvZmVzb3JhOiBEcmEuIERpYW5hIFBhb2xhIE1vbnRveWEgRXNjb2JhciwgZGlhbmEubW9udG95YUBpdGVzby5teCcKZGF0ZTogIkVuZXJvIDIwMjMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBkZXY6IGpwZWcKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0aGVtZTogY29zbW8KICAgIGhpZ2hsaWdodDogdGFuZ28Kc3VidGl0bGU6ICdTZW1hbmEgMTogQW5hbMOtdGljYSBiYXNhZGEgZW4gw6FyYm9sZXMgZGUgY2xhc2lmaWNhY2nDs24geSByZWdyZXNpw7NuJwotLS0KYGBge3Igc2V0dXAsIGVjaG8gPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gNykKYGBgCgo8c3R5bGU+Ci5mb3JjZUJyZWFrIHsgLXdlYmtpdC1jb2x1bW4tYnJlYWstYWZ0ZXI6IGFsd2F5czsgYnJlYWstYWZ0ZXI6IGNvbHVtbjsgfQo8L3N0eWxlPgoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL2RyeV90cmVlLnBuZyl7d2lkdGg9MTAlfQohW10oLi9pbWFnZXMvaXRlc28uanBlZyl7d2lkdGg9NSV9CgoKPC9jZW50ZXI+CgoKIyBJbnRyb2R1Y2Npw7NuCgpQb3IgbWVkaW8gZGUgbGEgY2llbmNpYSBkZSBkYXRvcyBub3MgYXl1ZGFtb3MgYSB0cmFuc2Zvcm1hciB5IGEgZW50ZW5kZXIgbG9zIGRhdG9zIHBhcmEgKnRvbWFyIGRlY2lzaW9uZXMqLiAgQW50ZXMgZGUgY29tZW56YXIgYSByZWFsaXphciBhbsOhbGlzaXMgZGUgZGF0b3MsIGRlYmVtb3MgcmVhbGl6YXIgdW5hIGV4cGxvcmFjacOzbiBkZSBlbGxvcywgKmVudGVuZGVyIGxvcyBkYXRvcyosIHBhcmEgZ2VuZXJhciBoaXDDs3Rlc2lzLCBwcm9iYXJsYXMgeSBsdWVnbyByZXBldGlybGFzIG8gcG9uZXJsYXMgZW4gbWFyY2hhLgoKUGFyYSByZWFsaXphciBtZWpvcmVzIGFuw6FsaXNpcyBzZSByZWNvbWllbmRhICplbCBjaWNsbyBkZSB2aWRhIGRlbCBwcm9jZXNvIGRlIGNpZW5jaWEgZGUgZGF0b3MgZW4gZXF1aXBvIFsoVERTUCldKGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvdGVhbS1kYXRhLXNjaWVuY2UtcHJvY2Vzcy9vdmVydmlldykqLCBlbCBjdWFsIGVzIHVuIHByb2Nlc28gcGFyYSBlc3RydWN0dXJhbCBsb3MgcHJveWVjdG9zIGRlIGNpZW5jaWEgZGUgZGF0b3MuIAoKRXN0ZSBjaWNsbyBkZSB2aWRhIGVzdMOhIGRpc2XDsWFkbyBwYXJhIHByb3llY3RvIGRlIGNpZW5jaWEgZGUgZGF0b3MgdmEgZGVzZGUgbGEgYWRxdWlzaWNpw7NuIGRlIGxvcyBkYXRvcyB5IGVudGVuZGltaWVudG8gZGVsIHByb2JsZW1hLCBoYXN0YSBsYSBwdWVzdGEgZW4gcHJvZHVjY2nDs24uIEVzdGFzIGFwbGljYWNpb25lcyBpbXBsZW1lbnRhbiBtb2RlbG9zIGRlIGFwcmVuZGl6YWplIG8gaW50ZWxpZ2VuY2lhIGFydGlmaWNpYWwgZGUgbcOhcXVpbmEgcGFyYSByZWFsaXphciB1biAqYW7DoWxpc2lzIHByZWRpY3Rpdm8vY2xhc2lmaWNhY2nDs24qLiBMb3MgcHJveWVjdG9zIGRlIGNpZW5jaWEgZGUgZGF0b3MgZXhwbG9yYXRvcmlvcyB5IGxvcyBwcm95ZWN0b3MgZGUgYW7DoWxpc2lzIGltcHJvdmlzYWRvcyB0YW1iacOpbiBzZSBwdWVkZW4gYmVuZWZpY2lhciBkZWwgdXNvIGRlIGVzdGUgcHJvY2Vzby4gUGVybyBwYXJhIGVzb3MgcHJveWVjdG9zLCBhbGd1bm9zIGRlIGxvcyBwYXNvcyBkZXNjcml0b3MgYSBjb250aW51YWNpw7NuIHB1ZWRlbiBubyBzZXIgbmVjZXNhcmlvcy4KCiMjIyBGYXNlcyBkZWwgY2ljbG8gZGUgdmlkYSBkZSBURFNQCgpFbCBjaWNsbyBkZSB2aWRhIGRlIFREU1Agc2UgY29tcG9uZSBwb3IgY2luY28gZmFzZXMgcHJpbmNpcGFsZXMgZGUgZm9ybWEgaXRlcmF0aXZhIChGaWd1cmEgMSk6CgoxLiBFbnRlbmRlciBlbCBuZWdvY2lvCgoyLiBBZHF1aXNpY2nDs24geSBjb21wcmVuc2nDs24gZGUgbG9zIGRhdG9zCgozLiBNb2RlbGFkbwoKNC4gSW1wbGVtZW50YWNpw7NuIAoKNS4gQWNlcHRhY2nDs24gZGVsIGNsaWVudGUgCgo8Y2VudGVyPgohW10oLi9pbWFnZXMvdGRzcC1saWZlY3ljbGUucG5nKQoKRmlndXJhIDEuIENpY2xvIGRlIHZpZGEgZGUgVERTUAo8L2NlbnRlcj4KCgojIyMgSGVycmFtaWVudGFzIGLDoXNpY2FzIGRlIGV4cGxvcmFjacOzbiBkZSBkYXRvcwoKTGEgZXhwbG9yYWNpw7NuIGRlIGRhdG9zIGVzIGVsIGFydGUgZGUgZW50ZW5kZXIgbG9zIGRhdG9zLCBnZW5lcmFyIGhpcMOzdGVzaXMgZSBpbnZlc3RpZ2FyIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGxvcyBkYXRvcyAodHJhbnNmb3JtYXIgcmVnaXN0cm9zIGVuIGluZm9ybWFjacOzbikuIEVsIG9iamV0aXZvIGRlIGxhIGV4cGxvcmFjacOzbiBkZSBkYXRvcyBlcyBleHRyYWVyIGluZm9ybWFjacOzbiB5IHRvbWFyIG1lam9yZXMgZGVjaXNpb25lcyBwYXJhIGVsIG1vZGVsbyBxdWUgc2UgdmEgYSBkZXNhcnJvbGxhci4KCkxhICp2aXN1YWxpemFjacOzbiBkZSBkYXRvcyosIGVzIHVuYSBkZSBsYXMgZm9ybWFzIGRlIGV4cGxvcmFjacOzbiBtw6FzIHV0aWxpemFkYXMuICBMYXMgdmlzdWFsaXphY2lvbmVzIHBvciBzaSBzb2xhcyBubyBzdWVsZW4gc2VyIHN1ZmljaWVudGVzLCBwb3IgbG8gcXVlIHNlIHZ1ZWx2ZSBuZWNlc2FyaW8gKnByb2Nlc2FyIHkgdHJhbnNmb3JtYXIgbG9zIGRhdG9zKiwgcG9yIG1lZGlvIGRlIGZyaWx0cmFkb3MsIGNyZWFjacOzbiBkZSBudWV2YXMgdmFyaWFibGVzIHkgY8OhbGN1bG9zIChpbmdlbmllcsOtYSBkZSBjYXJhY3RlcsOtc3RpY2FzKSwgcG9yIGVqZW1wbG8uCgpGaW5hbG1lbnRlLCBsYSBjb21iaW5hY2nDs24gZGUgbGEgdmlzdWFsaXphY2nDs24geSBsYSB0cmFuc2Zvcm1hY2nDs24gZGUgbG9zIGRhdG9zIGNvbiBzdSBjdXJpb3NpZGFkIHkgYW7DoWxpc2lzLCBub3MgbGxldmFyw6EgYSBsYSBleHRyYWNjacOzbiBkZSBpbmZvcm1hY2nDs24gcmVzb2x1Y2nDs24gZGUgcHJlZ3VudGFzIGEgY2VyY2EgZGUgbG9zIGRhdG9zLgoKIyBWaXN1YWxpemFjacOzbiBkZSBkYXRvcwoKPGNlbnRlcj4KKuKAnFRoZSBzaW1wbGUgZ3JhcGggaGFzIGJyb3VnaHQgbW9yZSBpbmZvcm1hdGlvbiB0byB0aGUgZGF0YSBhbmFseXN04oCZcyBtaW5kIHRoYW4gYW55IG90aGVyIGRldmljZS7igJ0g4oCUIEpvaG4gVHVrZXkqCjwvY2VudGVyPgoKQ29tZW5jZW1vcyBhIHV0aWxpemFyICpSX1N0dWRpbyogeSBhbGd1bmFzIGRlIGxhcyBwYXF1ZXRlcsOtYXMgcXVlIG5vcyBheXVkYXLDoW4gYSBlbnRlbmRlciBsb3MgZGF0b3MuIFBhcmEgbGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MgdXRpbGl6YXJlbW9zIGxhIHBhcXVldGVyw61hIGBnZ3Bsb3QyYCwgYWNsYXJhbmRvIHF1ZSBubyBlcyBsYSDDum5pY2EgaGVycmFtaWVudGEgcGFyYSBoYWNlcmxvLiBBcXXDrSBwdWVkZXMgZGVzY2FyZ2FyIGFsZ3Vub3MgdHJ1Y29zIGRlIGdncGxvdCBbKGBnZ3Bsb3QyYCBjaGVhdHNoZWV0cykuXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL2RhdGEtdmlzdWFsaXphdGlvbi0yLjEucGRmKQoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL2RhdGEtdmlzdWFsaXphdGlvbi5wbmcpe3dpZHRoPTcwJX0KCkZpZ3VyYSAyLiBDaGVhdCBTaGVldCAoaG9qYSBkZSB0cnVjb3MpIGRlIGBnZ3Bsb3QyYAo8L2NlbnRlcj4KClVuYSBkZSBsYXMgcGFxdWV0ZXLDrWFzIHByaW5jaXBhbGVzIHF1ZSB2YW1vcyBhIHV0aWxpemFyIGVzIGB0aWR5dmVyc2VgIHkgbGEgY3VhbCBlcyBpbmRpc3BlbnNhYmxlIHBhcmEgZWwgZnVuY2lvbmFtaWVudG8gZGUgYGdncGxvdDJgLiBBaG9yYSBsYSBwcmVndW50YSBlcyAqwr9xdcOpIGVzIGB0aWR5dmVyc2VgLD8qIGEgY29udGludWFjacOzbiAgaGFibGFyZW1vcyBzb2JyZSBlc3RhIHBhcXVldGVyw61hLgoKIyMgwr9RdcOpIGVzIGB0aWR5dmVyc2VgPwoKRWwgW2B0aWR5dmVyc2VgXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgZXMgdW4gY29uanVudG8gZGUgcGFxdWV0ZXMgZW4gKlIqIGRpc2XDsWFkb3MgcGFyYSBjaWVuY2lhIGRlIGRhdG9zLiBUb2RvcyBsb3MgcGFxdWV0ZXMgY29tcGFydGVuIHVuYSBmaWxvc29mw61hIGRlIGRpc2XDsW8sIHVuYSBncmFtw6F0aWNhIHkgZXN0cnVjdHVyYXMgZGUgZGF0b3Mgc3VieWFjZW50ZXMuCgpUb2RvcyBlc3RvcyBwYXF1ZXRlcyBub3MgYXl1ZGFuIGEgaW1wb3J0YXIsIG9yZGVuYXIsIHRyYW5zZm9ybWFyIHkgYXPDrSBlbnRlbmRlciBsb3MgZGF0b3MsIHBhcmEgZmluYWxtZW50ZSBwb2RlciBvYnRlbmVyIHkgY29tdW5pY2FyIHN1cyByZXN1bHRhZG9zIGRlIGZvcm1hIG3DoXMgZsOhY2lsLgoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL3JfdGlkeXZlcnNlLmpwZyl7d2lkdGg9NjAlfQoKRmlndXJhIDMuIGB0aWR5dmVyc2VgCgo8L2NlbnRlcj4KClNlIHB1ZWRlbiBpbnN0YWxhciB0b2RvcyBsb3MgcGFxdWV0ZXMgZGVzZGUgYHRpZHl2ZXJzZWAgc2ltcGxlbWVudGUgZXNjcmliaWVuZG8gZW4gdHUgY29uc29sYSBkZSAqUiogYGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpYC4gCgpgYGB7ciBpbnN0YWxsIHRpZHl2ZXJzZSwgZXZhbD1GQUxTRX0KIyBTw7NsbyBzZSBuZWNlc2l0YSBpbnN0YWxhciBsYSBwcmltZXJhIHZleiBlc3RlIHBhcXVldGUgZW4gdHUgY29tcHV0YWRvcmEKaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKYGBgCgpQYXJhIHF1ZSBmdW5jaW9uZSBlc3RlIHBhcXVldGUsIHNpZW1wcmUgZW4gbnVlc3RybyBwcm95ZWN0byBkZWJlbW9zIGRlIGNhcmdhciBzdSBsaWJyZXLDrWEgY29ycmllbmRvIGVsIHNpZ3VpZW50ZSBjw7NkaWdvOgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCkVuIGVsIGPDs2RpZ28sIHNpIHNlIG5lY2VzaXRhIHNlciBleHBsaWNpdG8gZGUgdW5hIGZ1bmNpw7NuIG8gZGF0YXNldCBkZSB1biBwYXF1ZXRlIGVuIHBhcnRpY3VsYXIsIHBvZGVtb3MgdXNhciBsYSBmdW5jacOzbiBgZm9ybV9wYWNrYWdlOjpmdW5jdGlvbigpYC4gUG9yIGVqZW1wbG8sIGBnZ3Bsb3QyOjpnZ3Bsb3QoKWAgZGljZSBxdWUsIGV4cGzDrWNpdGFtZW50ZSwgc2UgcXVpZXJlIHVzYXIgbGEgZnVuY2nDs24gIGBnZ3Bsb3QoKWAgZGVsIHBhcXVldGUgIGBnZ3Bsb3QyYC4KCkhheSBtdWNob3MgcGFxdWV0ZXMgcXVlIHVzYXJlbW9zIHF1ZSBjdW1wbGVuIGNvbiBsYSBlc3RydWN0dXJhIGRlIGxvcyBkYXRvcyB5IGxhcyBwcsOhdGljYXMgZGUgYHRpZHl2ZXJzZWAgcXVlIGRlYmVuIGluc3RhbGFyc2UgZGUgZm9ybWEgaW5kZXBlbmRpZW50ZSB1c2FuZG8gZWwgY29tYW5kbyBgaW5zdGFsbC5wYWNrYWdlcygicGFja2FnZSIpYCB5IGx1ZWdvIGNhcmdhciBzdSBsaWJyZXLDrWEuIEFsZ3Vub3MgZGUgbG9zIHF1ZSB1c2FyZW1vcyBzZXLDoSBbYGx1YnJpZGF0ZWBdKGh0dHBzOi8vbHVicmlkYXRlLnRpZHl2ZXJzZS5vcmcvKSB5IFtgdHNpYmJsZWBdKGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvdHNpYmJsZS92aWduZXR0ZXMvaW50cm8tdHNpYmJsZS5odG1sKSwgcXVlIHNvbiBwYXF1ZXRlcyBlc3BlY2lhbGVzIHBhcmEgZmVjaGFzLgoKPGNlbnRlcj4KIVtdKC4vaW1hZ2VzL2x1YnJpZGF0ZS5wbmcpe3dpZHRoPTE1JX0KIVtdKC4vaW1hZ2VzL3RzaWJibGUucG5nKXt3aWR0aD0xNSV9CjwvY2VudGVyPgoKRWwgY29tYW5kbyBgJT4lYCBxdWUgc2UgY29ub2NlIGNvbW8gKnBpcGUqIG8gZW4gZXNwYcOxb2wgKnR1YmVyw61hKiwgZXMgdW4gY29tYW5kbyBxdWUgc8OzbG8gZnVuY2lvbmEgY29uIGxhIGxpYnJlcsOtYSBkZSBgdGlkeXZlcnNlYC4gIFNlIHV0aWxpemEgcGFyYSBlbmZhdGl6YXIgdW5hIHNlY3VlbmNpYSBkZSBhY2Npb25lcyBjb21lbnphbmRvIGNvbiB1biBjb25qdW50byBkZSBkYXRvcy4gRGViZSBkZSBlc3RhciBhbnRlY2VkaWRvIGRlIHVuYSBsw61uZWEgeSBlcyBtw6FzIGbDoWNpbCBkZSBlbnRlbmRlciBzaSBzZSBoYWNlbiBhY2Npb25lcyBwb3IgbMOtbmVhcy4gU2UgcHVlZGUgbGVlciBlc3RlIGNvbWFuZG8gZW4gZWwgY8OzZGlnbyBjb21vIHNpIGZ1ZXJhIHVuICpsdWVnbyosIHF1w6kgZXMgbG8gcXVlIHNpZ3VlLCBlbiBsYSBlamVjdWNpw7NuIHkgcmVhbGl6YXIgZW4gdW5hIHNvbGEgbMOtbmVhIHZhcmlhcyBhY2Npb25lcy4gCgpQYXJhIGxvcyBlamVtcGxvIHVzYXJlbW9zIHVuYSBiYXNlIGRlIGRhdG9zIGRlIFtjcsOpZGl0b3MgZGUgQWxlbWFuaWEgXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3VjaW1sL2dlcm1hbi1jcmVkaXQ/cmVzb3VyY2U9ZG93bmxvYWQmc2VsZWN0PWdlcm1hbl9jcmVkaXRfZGF0YS5jc3YpLCBsb3MgY3VhbGVzIHNlIGV4dHJhZ2Vyb24gZGUgYGthZ2dsZWAuIAoKVW4gcmVzdW1lbiBkZSBsYSBkZXNjcmlwY2nDs24gZGUgbGFzIGNhdGVnb3LDrWFzIHNlIHByZXNlbnRhIGEgY29udGludWFjacOzbjoKLSBFZGFkIChudW3DqXJpY28pCi0gU2V4byAodGV4dG86IG1hc2N1bGlubywgZmVtZW5pbm8pCi0gVHJhYmFqbyAobnVtw6lyaWNvOiAwIC0gbm8gY3VhbGlmaWNhZG8geSBubyByZXNpZGVudGUsIDEgLSBubyBjdWFsaWZpY2FkbyB5IHJlc2lkZW50ZSwgMiAtIGN1YWxpZmljYWRvLCAzIC0gbXV5IGN1YWxpZmljYWRvKQotIFZpdmllbmRhICh0ZXh0bzogcHJvcGlhLCBkZSBhbHF1aWxlciBvIGdyYXR1aXRhKQotIEN1ZW50YXMgZGUgYWhvcnJvICh0ZXh0bzogcG9jbywgbW9kZXJhZG8sIGJhc3RhbnRlLCBtdWNobykKLSBDdWVudGEgY29ycmllbnRlIChudW3DqXJpY28sIGVuIERNIC0gbWFyY28gYWxlbcOhbikKLSBJbXBvcnRlIGRlbCBjcsOpZGl0byAobnVtw6lyaWNvLCBlbiBETSkKLSBEdXJhY2nDs24gKG51bcOpcmljbywgZW4gbWVzZXMpCi0gRmluYWxpZGFkICh0ZXh0bzogY29jaGUsIG11ZWJsZXMvZXF1aXBhbWllbnRvLCByYWRpby9UViwgZWxlY3Ryb2RvbcOpc3RpY29zLCByZXBhcmFjaW9uZXMsIGVkdWNhY2nDs24sIG5lZ29jaW9zLCB2YWNhY2lvbmVzL290cm9zKQoKVHJhZHVjY2nDs24gcmVhbGl6YWRhIGNvbiBsYSB2ZXJzacOzbiBncmF0dWl0YSBkZWwgdHJhZHVjdG9yIHd3dy5EZWVwTC5jb20vVHJhbnNsYXRvcgoKYGBge3J9CmRhdGEgPC0gcmVhZC5jc3YoImh0dHBzOi8vZGF0YWh1Yi5pby9tYWNoaW5lLWxlYXJuaW5nL2NyZWRpdC1nL3IvY3JlZGl0LWcuY3N2IikKaGVhZChkYXRhKQpgYGAKClBvZGVtb3MgaW50ZXJwcmV0YXIgZWwgcGlwZSBjb24gbGEgcGFsYWJyYSAqbHVlZ28qLiBQb3IgZWplbXBsbywgdG9tYW5kbyBsb3MgZGF0b3MgZGUgY3LDqWRpdG8gYW50ZXJpb3IgeSBxdWVyZW1vcyB1biBmaWx0cm8gZGUgbGEgdmFyaWJhbGUgYGhvdXNpbmdgIHBvciBgb3duYCwgdXNhbmRvIFIgYmFzZSB5IGx1ZWdvIHVzYW5kbyBgdGlkaXZlcnNlYCBjb24gZWwgY29tYW5kbyBgICU+JSBgOgoKYGBge3J9CiMgUiBiYXNlCmhlYWQoZmlsdGVyKGRhdGEsaG91c2luZyA9PSAnb3duJykpCmBgYAoKYGBge3J9CiMgVGlkaXZlcnNlIGNvbiBlbCBwaXBlICU+JSAKZGF0YSAlPiUgZmlsdGVyKGhvdXNpbmcgPT0gJ293bicpICU+JSBoZWFkKCkKYGBgCgpFbiBsYSDDumx0aW1hIGzDrW5lYSBvYnRlbmVtb3MgZWwgbWlzbW8gcmVzdWx0YWRvIHF1ZSBzaSBsbyBoYWNlbW9zIGNvbiBSIGJhc2UsIHNvbG8gcXVlIHBvZGVtb3MgaXIgaW50ZXJwcmV0YW5kbyBtw6FzIGbDoWNpbG1lbnRlIGVsIGPDs2RpZ28uIEVzdGUgc2UgcHVlZGUgbGVlciBjb21vIHRvbWEgYGRhdGFgIHkgbHVlZ28gYXBsaWNhIHVuIGZpbHRybyBhIGxhIHZhcmlhYmxlIGBTZXggPSAnbWFsZSdgIHkgbHVlZ28gbXVlc3RyYSBsb3MgcHJpbWVyb3MgNiByZWdpc3Ryb3MgKGVuY2FiZXphZG8pLgoKVW4gZWplbXBsbyBkZSB1dGlsaXphY2nDs24gZGVsIHBpcGUgc2UgcHVlZGUgc2VyOgoKYGBge3J9CmRhdGEgJT4lIGdyb3VwX2J5KGNsYXNzLHBlcnNvbmFsX3N0YXR1cykgJT4lIAogIHN1bW1hcmlzZShuPSBuKCksIGF2Z19leGlzdGluZ19jcmVkaXRzID0gbWVhbihleGlzdGluZ19jcmVkaXRzKSkgJT4lIAogIGFycmFuZ2UocGVyc29uYWxfc3RhdHVzLC1uKQpgYGAKCiMjIFZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGNvbiBgZ2dwbG90MmAKCjxjZW50ZXI+CiFbXSguL2ltYWdlcy9nZ3Bsb3QucG5nKXt3aWR0aD0xNSV9CgpQYXJhIGVqZW1wbGlmaWNhciBsYSB2aXN1YWxpemFjacOzbiBlbiAqUiogYW5hbGl6YXJlbW9zIGVsIGNvbmp1bnRvIGRlIGRhdG9zICh2YXJpYWJsZXMgZW4gbGFzIGNvbHVtbmFzIHkgb2JzZXJ2YWNpb25lcyBlbiBsYXMgZmlsYXMpIHF1ZSBjb250aWVuZSBkYXRvcyByZWNvcGlsYWRvcyBkZSBsYSAqQWdlbmNpYSBkZSBQcm90ZWNjacOzbiBBbWJpZW50YWwgZGUgRXN0YWRvcyBVbmlkb3MqIHBhcmEgMzggbW9kZWxvcyBkZSBhdXRvcy4gIEVzdGUgZGF0YSBmcmFtZSBzZSBlbmN1ZW50cmEgZW4gUiBjb24gbGEgZnVuY2nDs24gYG1wZ2AsIGVsIG5vbWJyZSBkZSBlc3RlIGRhdGEgZnJhbWUgdmllbmUgZGUgbGEgYWJyZXZpYWNpw7NuIGRlIChtaWxlcyBwZXIgZ2FsbG9uIG8gZW4gZXNwYcOxb2wsIG1pbGxhcyBwb3IgZ2Fsw7NuIChtcGcpKS4gUGFyYSB0ZW5lciBtYXlvciBpbmZvcm1hY2nDs24gc29icmUgYG1wZ2AsIHB1ZWRlbiBhYnJpciBheXVkYSB5IGJ1c2NhciBlc3RhIGZ1bmNpw7NuIG8gZW4gc3UgY29uc29sYSBjb3JyZXIgZWwgY29tYW5kbyBgP21wZ2AuCgpQYXJhIG1pcmFyIGN1w6FsZXMgc29uIGxhcyB2YXJpYWJsZXMgKG5vbWJyZSBsYXMgY29sdW1uYXMpIGxvIHBvZGVtb3MgaGFjZXIgY29uIGxhIGZ1bmNpw7NuIGBjb2xuYW1lcygpYCB5IHBhcmEgc2FjYXIgdW4gcmVzdW1lbiBkZSBudWVzdHJvcyBkYXRvcyB1dGlsaXphbW9zIGxhIGZ1bmNpw7NuIGBzdW1tYXJ5KClgLgoKYGBge3J9CmNvbG5hbWVzKG1wZykKc3VtbWFyeShtcGcpCmBgYAoKUXVlcmVtb3MgcmVzcG9uZGVyIGxhIHByZWd1bnRhOiAqKirCv0xvcyBhdXRvcyBjb24gbW90b3IgbcOhcyBncmFuZGVzIHVzYW4gbcOhcyBjb21idXN0aWJsZSBxdWUgbG9zIGF1dG9zIGNvbiBtb3RvciBtw6FzIHBlcXVlw7FvLj8qKiogUHJvYmFibGVtZW50ZSB5YSB0ZW5nYSB1bmEgcmVzcHVlc3RhLCBwZXJvLCBpbnRlbnRlbW9zIHF1ZSBzdSByZXNwdWVzdGEgc2VhIHByZWNpc2EuIMK/Q8OzbW8gc2UgdmUgbGEgcmVsYWNpw7NuIGVudHJlIGVsIHRhbWHDsW8gZGVsIG1vdG9yIHkgbGEgZWZpY2llbmNpYSBkZWwgY29tYnVzdGlibGU/IMK/RXMgcG9zaXRpdm8/IMK/ZXMgbmVnYXRpdm8/IMK/ZXMgbGluZWFsPyDCv2VzIG5vIGxpbmVhbC4/CgpIYXkgZG9zIHZhcmlhYmxlcyBlbiBudWVzdHJvICpkYXRhIGZyYW1lKiBkZSBgbXBnYCBxdWUgbm9zIHBvZHLDrWFuIGF5dWRhciBhIHJlc3BvbmRlcjoKCjEuICBgZGlzcGxgIGluZGljYSBlbCB0YW1hw7FvIGRlbCBtb3RvciBlbiBsaXRyb3MKCjIuICBgaHd5YCBpbmRpY2EgbGEgZWZpY2llbmNpYSBkZSBjb21idXN0aWJsZSBkZWwgYXV0byBlbiBjYXJyZXRlcmEgZW4gbWlsbGFzIHBvciBnYWzDs24gKG1wZykKClBhcmEgcmVhbGl6YXIgZWwgZ3LDoWZpY28gdmFtb3MgYSBwb25lciBsYSB2YXJpYWJsZSBkZWwgdGFtYcOxbyBkZWwgbW90b3IgKGBkaXNwbGApIGVuIGVsIGVqZSAqeCogeSBsYSBlZmljaWVuY2lhIChgaG15YCkgZW4gZWwgZWplICp5KiB5IHV0aWxpemFyZW1vcyBsYSBwYXF1ZXRlcsOtYSBgZ2dwbG90MmAuCgpgYGB7cn0KIyBDYXJnYXIgbGEgcGFxdWV0ZXLDrWEgZGUgZ2dwbG90MgpsaWJyYXJ5KCJnZ3Bsb3QyIikKCiMgR3JhZmljbwptcGcgJT4lICBnZ3Bsb3QoYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpIApgYGAKCgpFbiBlbCBjb21hbmRvIGFudGVyaW9yIHByaW1lcm8gZXN0YW1vcyB0b21hbmRvIGxvcyBkYXRvcyBgbXBnYCwgbHVlZ28gYCU+JWAgdG9tYW1vcyBsYSBmdW5jacOzbiBgZ2dwbG90KClgIHkgbGUgZXN0YW1vcyBlc3BlY2lmaWNhbmRvIGxvcyBlamVzIGNvbiBsYSBmdW5jacOzbiBgYWVzKClgLCBwZXJvIGHDum4gbm8gbGUgaGVtb3MgZXNwZWNpZmljYWRvIHF1w6kgdGlwbyBkZSBncsOhZmljbyBzZXLDoSwgZXMgcG9yIGVsbG8gcXVlIG5vIG5vcyBtdWVzdHJhIG5pbmd1bmEgdmlzdWFsaXphY2nDs24uIAoKU2kgcG9yIGVqZW1wbG8gcXVlcmVtb3MgcXVlIGNhZGEgZGF0byBsbyBtdWVzdHJlIGNvbW8gdW4gcHVudG8sIHZhbW9zIGEgYWRpY2lvbmFyIGFsIGBnZ3Bsb3RgIGNvbiB1biBtw6FzIChgK2ApIGxhIGZ1bmNpw7NuIGBnZW9tX3BvaW50KClgLiBQYXJhIHZlciBvdHJvcyB0aXBvcyBkZSBncsOhZmljb3Mgbm9zIHBvZGVtb3MgYXBveWFyIGRlIGxhIGhvamEgZGUgdHJ1Y29zIGRlIGBnZ3Bsb3QyYCBwcmVzZW50YWRhIGVuIGxhIHNlc2nDs24gZGUgKmludHJvZHVjY2nDs24qLgoKYGBge3IgYmFzaWMgZ2dwbG90LCBmaWcuYWxpZ249J2NlbnRlcid9CiMgR3JhZmljbwptcGcgJT4lICBnZ3Bsb3QoYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsgCiAgZ2VvbV9wb2ludCgpCmBgYAoKUG9yIGxvIHRhbnRvLCBhIGNvbnRpbnVhY2nDs24gbW9zdHJhbW9zIGRlIGZvcm1hIGdlbmVyYXIgbG9zIHBhc29zIHBhcmEgZGVzY3JpYmlyIGPDs21vIGZ1bmNpb25hIHVuIGdyw6FmaWNvIGBnZ3Bsb3QyYDoKCjEuIENvbWVuY2Vtb3MgY29uIHVuIG9iamV0byBgZ2dwbG90ICgpYCwgZG9uZGUgZXNwZWNpZmlxdWUgbG9zIGRhdG9zIHF1ZSBzZSB1dGlsaXphcsOhbiwKCjIuIHByb3BvcmNpb25hciBlbCBtYXBlbyBlc3TDqXRpY28gKGNvbiBgYWVzICgpYCksCgozLiBhZ3JlZ3VlbW9zIGNhcGFzOgoKICAgICAqIFNpIGRlc2VhIHVuIGRpYWdyYW1hIGRlIGRpc3BlcnNpw7NuLCB1c2UgYGdlb21fcG9pbnQgKClgLCBoaXN0b2dyYW1hIGBnZW9tX2hpc3QgKClgLiBPdHJvcyBncsOhZmljb3MgY29tdW5lcyBzb24gYGdlb21fbGluZSAoKWAsIGBnZW9tX2JhciAoKWAsIGBnZW9tX2JveHBsb3QgKClgLgogICAgIAogICAgICogZGVmaW5pciBlc2NhbGFzIGRlIGNvbG9yLCBjb21vIGBzY2FsZV9jb2xvcl9icmV3ZXIgKClgIG8gYHNjYWxlX2NvbG9yX2Rpc3RpbGxlciAoKWAsCiAgICAgCiAgICAgKiBlc3BlY2lmaWNhY2lvbmVzIGRlIGZhY2V0YXMgYGZhY2V0X3dyYXAgKClgIG8gYGZhY2V0X2dyaWQgKClgCiAgICAgCiAgICAgKiBzaXN0ZW1hcyBkZSBjb29yZGVuYWRhcywgY29tbyBgY29vcmRfY2FydGVzaWFuICgpYCwgYGNvb3JkX2ZsaXAgKClgCgpDYWRhIGVsZW1lbnRvIGVzdMOhIHNlcGFyYWRvIGNvbiB1biBzaWdubyBtw6FzICgqKiArICoqKTogCgoKYGBge3IgY3VzdG9tIGdncGxvdCwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9OH0KZ2dwbG90KG1wZywgYWVzKGRpc3BsLCBod3ksIGNvbG91ciA9IGNsYXNzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+bWFudWZhY3R1cmVyKQpgYGAKCkRlYmVtb3MgdGVuZXIgZW4gY3VlbnRhIHF1ZSBgZ2dwbG90MmAgdHJhYmFqYSBwb3IgY2FwYXMsIHNpIGRlc2VhbW9zIHF1ZSB0b2RvIGVsIGdyw6FmaWNvIHRlbmdhIGFsZ28gZGViZW1vcyBkZSBoYWNlcmxvIGRlc2RlIGxhIGZ1bmNpw7NuIHByaW1hcmlhIGBnZ3Bsb3QoKWAsIGRlIGxvIGNvbnRyYXJpbyBsbyBwb2RlbW9zIGhhY2VyIGVuIGxhcyBjYXBhcyBzdWJ5YWNlbnRlcy4gUGFyYSBlbnRlbmRlciBlc3RhIGRpZmVyZW5jaWEgdmVhbW9zIGxhcyBkb3Mgc2lndWllbnRlcyB2aXN1YWxpemFjaW9uZXM6CgpgYGB7ciBnZ3Bsb3QgYWVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CiMgRWwgY29sb3IgYXBsaWNhIGEgdG9kbyBlbCBncsOhZmljbyBwb3JxdWUgZXN0w6EgZW4gbGEgZnVuY2nDs24gcHJpbmNpcGFsIGdncGxvdCgpCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5LGNvbG9yID0gY2xhc3MpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aCgpCgojIEVsIGNvbG9yIHPDs2xvIGFwbGljYSBhIGxvcyBwdW50b3MgeSBubyB0b21hIGVuIGN1ZW50YSBnZW9tX3Ntb290aCgpCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2xhc3MpKSArCiAgZ2VvbV9zbW9vdGgoKQpgYGAKCiMgQW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBkYXRvcyB7I0VEQX0KCkxhIGV4cGxvcmFjacOzbiBkZSBsb3MgZGF0b3Mgbm9zIHB1ZWRlIHByb3BvcmNpb25hciBpbmZvcm1hY2nDs24gZGluw6FtaWNhLCBlbmNvbnRyYXIgcGF0cm9uZXMgeSBlbnRlbmRlciBsYSBoaXN0b3JpYSBkZSBsb3MgZGF0b3MuICBFcyBpbXBvcnRhbnRlLCBhbnRlcyBkZSBjb21lbnphciBhIGVudHJhciBlbiBlbCBtb2RlbG8sIHJlYWxpemFyIHVuICpBbsOhbGlzaXMgRXhwbG9yYXRvcmlvIGRlIGxvcyBkYXRvcyogbyBlbiBpbmdsw6lzICpFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzKiAqKiooRURBKSoqKi4KClBhcmEgcmVhbGl6YXIgZXN0ZSBhbsOhbGlzaXMsIGRlYmVtb3MgaGFjZXIgaGlww7N0ZXNpcyByZXNwZWN0byBhIGxvcyBkYXRvcywgdHJhbnNmb3JtYXIgZSBpbnNwZWNjaW9uYXIgZGUgZm9ybWEgdmlzdWFsIGxhcyBwcm9waWVkYWRlcyBlc3RhZMOtc3RpY2FzIGRlIGxvcyBkYXRvcy4KCiogQ3XDoWwgZXMgZWwgY29tcG9ydGFtaWVudG8gZW4gZWwgdGllbXBvIGRlIGxhIHZhcmlhYmxlIChzaSBhcGxpY2EpLAoKKiBjdcOhbCBlcyBsYSB2YXJpYWNpw7NuIGRlIGxhIHZhcmlhYmxlLAoKKiBpZGVudGlmaWNhY2nDs24gZGUgdmFsb3JlcyBhdMOtcGljb3MsCgoqIGRpc3RyaWJ1Y2lvbmVzIHkgdmFyaWFibGVzIGVzdGFkw61zdGljYXMuCgpBbGd1bm9zIGVqZW1wbG9zOgoKUGFyYSBjb21wYXJhciB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHBvZGVtb3MgdXNhciBkaWFncmFtYXMgZGUgYmFycmFzIChgZ2VvbV92YXIoKWApOgpgYGB7ciBiYXJwbG90fQpkaWFtb25kcyAlPiUgIGdncGxvdCgpICsKICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBjdXQpKSArCiAgZ2d0aXRsZSgiQ291bnQgb2YgRGlhbW9uZHMgYnkgY3V0IHF1YWxpdHkiKQpgYGAKCk90cmEgb3BjacOzbiBwYXJhIGVudGVuZGVyIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIGVzIGhhY2VyIGRpYWdyYW1hcyBkZSBjYWphcy9iaWdvdGVzIG8gYm94cGxvdHM6CmBgYHtyIGJveHBsb3R9Cm1wZyAgJT4lIGdncGxvdCgpICsKICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh4ID0gcmVvcmRlcihjbGFzcywgaHd5LCBGVU4gPSBtZWRpYW4pLCB5ID0gaHd5KSkrIGxhYnMoeCA9ICJjbGFzcyIsIHkgPSAiaHd5IG1wZyIpCmBgYAoKUGFyYSB2YXJpYWJsZXMgY29udGludWFzLCBwb2RlbW9zIHV0aWxpemFyIHVuIGhpc3RvZ3JhbWEgZGUgZnJlY3VlbmNpYXMsIGVzcGVjaWZpY2FuZG8gZWwgdGFtYcOxbyBkZSBsYSBjbGFzZSBjb24gYGJpbndpZHRoYDoKCmBgYHtyIGhpc3R9CmRpYW1vbmRzICU+JSBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0obWFwcGluZyA9IGFlcyh4ID0gY2FyYXQpLCBiaW53aWR0aCA9IDAuNSkgKwogIGdndGl0bGUoIkhpc3RvZ3JhbSBvZiBjYXJhdHMiKQpgYGAKClNpIHF1ZXJlbW9zIHZpc3VhbGl6YXIgaGlzdG9ncmFtYXMgZGUgbcO6bHRpcGxlcyB2YXJpYWJsZXMgZW4gdW4gc8OzbG8gZ3LDoWZpY28sIHNlIHB1ZWRlIHVzYXIgbGEgZnVuY2nDs24gYGdlb21fZnJlcXBvbHkoKWAgbyB1c2FyIGZhY2V0YXMgZGVudHJvIGRlIGdncGxvdDJgYDoKCgpgYGB7ciBmcmVxcG9seX0KZ2dwbG90KGRhdGEgPSBkaWFtb25kcyAlPiUgZmlsdGVyKGNhcmF0IDwgMyksIG1hcHBpbmcgPSBhZXMoeCA9IGNhcmF0LCBjb2xvdXIgPSBjdXQpKSArCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDAuMSkKYGBgCgpgYGB7ciBoaXN0ICsgZmFjZXR9CmdncGxvdChkYXRhID0gZGlhbW9uZHMgJT4lIGZpbHRlcihjYXJhdCA8IDMpLCBtYXBwaW5nID0gYWVzKHggPSBjYXJhdCwgZmlsbCA9IGN1dCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMSkgKwogIGZhY2V0X3dyYXAofiBjdXQpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKYGBge3IgaGlzdCBiaW53aWR0aCwgZmlnLndpZHRoPTgsZmlnLmhlaWdodD04fQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShncmlkKQoKZyA8LSBnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzICU+JSBmaWx0ZXIoY2FyYXQgPCAzKSwgbWFwcGluZyA9IGFlcyh4ID0gY2FyYXQpKQpnMCA8LSBnICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUpICsKICBnZ3RpdGxlKCJCaW53aWR0aCA9IDAuNSIpCmcxIDwtIGcgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMSkgKwogIGdndGl0bGUoIkJpbndpZHRoID0gMC4xIikKZzIgPC0gZyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkgKwogIGdndGl0bGUoIkJpbndpZHRoID0gMC4wMSIpCgpncmlkLmFycmFuZ2UoZzAsZzEsZzIsIAogICAgICAgICAgICAgdG9wID0gIkhpc3RvZ3JhbXMgdmFyeWluZyB0aGUgYmlud2lkdGgiLAogICAgICAgICAgICAgYm90dG9tID0gdGV4dEdyb2IoCiAgICAgICAgICAgICAiRGlmZmVyZW50IHBhdHRlcm5zIGNhbiBhcmlzZSB3aGVuIHNlbGVjdGluZyBkaWZmZXJlbnQgYmlud2lkdGhzIiwKICAgICAgICAgICAgIGdwID0gZ3Bhcihmb250ZmFjZSA9IDMsIGZvbnRzaXplID0gOSksCiAgICAgICAgICAgICBoanVzdCA9IDEsCiAgICAgICAgICAgICB4ID0gMSkpCmBgYAoKTG9zIGFudGVyaW9yZXMgc29uIHPDs2xvIGFsZ3Vub3MgZWplbXBsb3MsIHBlcm8gcG9kcsOtYSByZWFsaXphciBtdWNow61zaW1hcyBtw6FzIHZpc3VhbGl6YWNpb25lcyBjb21vIHNjYXR0ZXJwbG90cywgcG9yIGVqZW1wbG8uICAKCiMjIFJlcG9ydGUgZGUgZXhwbG9yYWNpw7NuIGRlIGRhdG9zCgo8Y2VudGVyPgohW10oLi9pbWFnZXMvZGF0YV9leHBsb3Jlci5wbmcpe3dpZHRoPTE1JX0KPC9jZW50ZXI+CgpUYW1iacOpbiBwb2RlbW9zIHJlYWxpemFyIHJlcG9ydGVzIGNvbXBsZXRvcyBjb24gYCBEYXRhRXhwbG9yZXI6OmNyZWF0ZV9yZXBvcnQoKWAuIFBhcmEgZWwgc2lndWllbnRlIGVqZW1wbG8gZGViZW1vcyBpbnN0YWxhciBsb3MgcGFxdWV0ZXMgYERhdGFFeHBsb3JlcmAgIHkgYG55Y2ZsaWdodHMxM2AgcGFyYSBsb3MgZGF0b3MgKGBpbnN0YWxsLnBhY2thZ2VzKCJueWNmbGlnaHRzMTMiKWApLgoKYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQpsaWJyYXJ5KERhdGFFeHBsb3JlcikKYGBgCgpFbiBlbCBwYXF1ZXRlIGBsaWJyYXJ5KERhdGFFeHBsb3JlcilgICBoYXkgNSBkYXRhIGZyYW1lczoKCiogYWlybGluZXMKCiogYWlycG9ydHMKCiogZmxpZ2h0cwoKKiBwbGFuZXMKCiogd2VhdGhlcgoKUG9kZW1vcyB2aXN1YWxpemFyIHN1IGVzdHJ1Y3R1cmEgZGUgbGEgc2lndWllbnRlIGZvcm1hOgoKYGBge3J9CmRhdGEgPC0gbGlzdChhaXJsaW5lcywgYWlycG9ydHMsIGZsaWdodHMsIHBsYW5lcywgd2VhdGhlcikKcGxvdF9zdHIoZGF0YSkKYGBgCgpQYXJhIHRlbmVyIHVuIHPDs2xvIGRhdGFzZXQgbcOhcyByb2J1c3RvIHBvZGVtb3MgZnVzaW9uYXIgbGFzIHRhYmxhcyBwb3IgbWVkaW8gZGUgbGEgZnVuY2nDs24gYG1lcmdlKClgCgpgYGB7cn0KZmluYWxfZGF0YSA8LSBmbGlnaHRzICU+JSBtZXJnZShhaXJsaW5lcywgYnk9ICJjYXJyaWVyIiwgYWxsLnggPSBUUlVFKSAlPiUgCiAgbWVyZ2UocGxhbmVzLCBieSA9ICJ0YWlsbnVtIiwgYWxsLnggPSBUUlVFLCBzdWZmaXhlcyA9IGMoIl9mbGlnaHRzIiwgIl9wbGFuZXMiKSkgJT4lIAogIG1lcmdlKGFpcnBvcnRzLCBieS54ID0gIm9yaWdpbiIsIGJ5LnkgPSAiZmFhIiwgYWxsLnggPSBUUlVFLCBzdWZmaXhlcyA9IGMoIl9jYXJyaWVyIiwgIl9vcmlnaW4iKSkgJT4lIAogIG1lcmdlKGFpcnBvcnRzLCBieS54ID0gImRlc3QiLCBieS55ID0gImZhYSIsIGFsbC54ID0gVFJVRSwgc3VmZml4ZXMgPSBjKCJfb3JpZ2luIiwgIl9kZXN0IikpCmBgYAoKIyMjIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MgY29uIGxhIHBhcXVldGVyw61hIGBEYXRhRXhwbG9yZXJgCgpQYXJhIGNvbm9jZXIgZWwgY29uanVudG8gZGUgZGF0b3MgcG9kZW1vcyByZWFsaXphciB1biBgc3VtbWFyeSgpYCBjb21vIGxvIGhpY2ltb3MgZW4gbGEgc2VzacOzbiBhbnRlcmlvciBvIHBvZGVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gYGludHJvZHVjZSgpYC4KCmBgYHtyfQppbnRyb2R1Y2UoZmluYWxfZGF0YSkKCiMgRGUgZm9ybWEgZ3LDoWZpY2EKcGxvdF9pbnRybyhmaW5hbF9kYXRhKQpgYGAKCkRlYmVtb3MgZGUgbm90YXIgYWxnbyBlbiBlc3RlIGNvbmp1bnRvIGRlIGRhdG9zOgoKKiBTw7NsbyBlbCAwLjI3JSBkZSBsYXMgZmlsYXMgZXN0w6FuIGNvbXBsZXRhcywKCiogdGVuZW1vcyA1LjclIGRlIG9ic2VydmFjaW9uZXMgZmFsdGFudGVzLCBlcyBkZWNpciwgZGFkbyBxdWUgc29sbyB0ZW5lbW9zIDAuMjclIGRlIGxhcyBmaWxhcyBjb21wbGV0YXMsIHNvbG8gaGF5IDUuNyUgZGUgb2JzZXJ2YWNpb25lcyBmYWx0YW50ZXMgZGVsIHRvdGFsLgoKRXN0b3MgdmFsb3JlcyBmYWx0YW50ZXMgbm9zIHBvZHLDoW4gZ2VuZXJhbCBwcm9ibGVtYXMgcGFyYSBhbmFsaXphciBsb3MgZGF0b3MsIHZlYW1vcyB1biBwb2NvIGxvcyBwZXJmaWxlcyBxdWUgZmFsdGFuLgoKIyMjIyBWYWxvcmVzIGZhbHRhbnRlcyAobWlzc2luZyB2YWx1ZXMpCgpTaWVtcHJlLCBlbiB0b2RvcyBsb3MgcHJvYmxlbWFzIHJlYWxlcywgdmFtb3MgYSB0ZW5lciBkYXRvcyBkZXNvcmRlbmFkb3MgeSBjb24gcHJvYmxlbWFzLiBQYXJhIHZpc3VhbGl6YXIgZWwgcGVyZmlsIGRlIGxvcyBkYXRvcyBmYWx0YW50ZXMgcG9kZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiBgcGxvdF9taXNzaW5nKClgLgoKYGBge3J9CnBsb3RfbWlzc2luZyhmaW5hbF9kYXRhKQpgYGAKClNpIGxlIGd1c3RhIG3DoXMgdGVuZXIgbGEgaW5mb3JtYWNpw7NuIGVuIGZvcm1hIGRlIHRhYmxhIHB1ZWRlIG9idGVuZXIgc3UgcGVyZmlsIHBvciBtZWRpbyBkZSBsYSBmdW5jacOzbiBgcHJvZmlsZV9taXNzaW5nKGZpbmFsX2RhdGEpYC4gCgpFbiBsYSB2aXN1YWxpemFjacOzbiBhbnRlcmlvciwgcG9kZW1vcyB2ZXIgcXVlIGxhIHZhcmlhYmxlIGBzcGVlZGAgZXMgbGEgcXVlIGVuIHN1IG1heW9yw61hIGxlIGZhbHRhIGluZm9ybWFjacOzbiwgYWwgcGFyZWNlciBlbmNvbnRyYW1vcyBlbCBjdWxwYWJsZSBkZSBxdWUgc8OzbG8gZWwgMC4yNyUgZGUgbnVlc3RyYXMgZmlsYXMgZXN0w6luIGNvbXBsZXRhcyB5IHByb2JhYmxlbWVudGUgZXN0YSB2YXJpYWJsZSBubyBzZWEgZGUgbXVjaGEgaW5mb3JtYWNpw7NuLiBQb3IgdGFudG8gbGEgcG9kZW1vcyBlbGltaW5hciBkZSBudWVzdHJvIGRhdGFmcmFtZS4KCmBgYHtyfQpmaW5hbF9kYXRhIDwtIGRyb3BfY29sdW1ucyhmaW5hbF9kYXRhLCAic3BlZWQiKQpgYGAKCiMjIyMgRGlzdHJpYnVjaW9uZXMKCkxhIHZpc3VhbGl6YWNpw7NuIGRlIGxhcyBkaXN0cmlidWNpb25lcyBkZSBmcmVjdWVuY2lhIHBhcmEgdG9kYXMgbGFzIGNhcmFjdGVyw61zdGljYXMgZGlzY3JldGFzIHNlIHB1ZWRlIHJlYWxpemFyIGNvbiBsYSBmdW5jacOzbiBgcGxvdF9iYXIoKWAuCgpgYGB7cn0KcGxvdF9iYXIoZmluYWxfZGF0YSkKYGBgCgpUcmFzIHVuYSBpbnNwZWNjacOzbiBkZXRhbGxhZGEgZGUgbGEgdmFyaWFibGUgYG1hbnVyYWN0dXJlcmAsIG5vIGVzIGbDoWNpbCBpZGVudGlmaWNhciBsYXMgc2lndWllbnRlcyBjYXJhY3RlcsOtc3RpY2FzIGR1cGxpY2FkYXM6CgoqIEFJUkJVUyBhbmQgQUlSQlVTIElORFVTVFJJRQoKKiBDQU5BREFJUiBhbmQgQ0FOQURBSVIgTFRECgoqIE1DRE9OTkVMTCBET1VHTEFTLCBNQ0RPTk5FTEwgRE9VR0xBUyBBSVJDUkFGVCBDTyBhbmQgTUNET05ORUxMIERPVUdMQVMgQ09SUE9SQVRJT04KClBvciB0YW50bywgZGViZW1vcyBwcm9jZWRlciBhIGxpbXBpYXIgZXN0YSB2YXJpYWJsZQoKYGBge3J9CmZpbmFsX2RhdGFbd2hpY2goZmluYWxfZGF0YSRtYW51ZmFjdHVyZXIgPT0gIkFJUkJVUyBJTkRVU1RSSUUiKSxdJG1hbnVmYWN0dXJlciA8LSAiQUlSQlVTIgpmaW5hbF9kYXRhW3doaWNoKGZpbmFsX2RhdGEkbWFudWZhY3R1cmVyID09ICJDQU5BREFJUiBMVEQiKSxdJG1hbnVmYWN0dXJlciA8LSAiQ0FOQURBSVIiCmZpbmFsX2RhdGFbd2hpY2goZmluYWxfZGF0YSRtYW51ZmFjdHVyZXIgJWluJSBjKCJNQ0RPTk5FTEwgRE9VR0xBUyBBSVJDUkFGVCBDTyIsICJNQ0RPTk5FTEwgRE9VR0xBUyBDT1JQT1JBVElPTiIpKSxdJG1hbnVmYWN0dXJlciA8LSAiTUNET05ORUxMIERPVUdMQVMiCgpwbG90X2JhcihmaW5hbF9kYXRhJG1hbnVmYWN0dXJlcikKCmBgYApBZGljaW9uYWxtZW50ZSwgbGFzIHZhcmlhYmxlcyBgZHN0X29yaWdpbmAsIGB0em9uZV9vcmlnaW5gLCBgeWVhcl9mbGlnaHRzYCB5IGB0el9vcmlnaW5gIGNvbnRpZW5lbiB1biBzb2xvIHZhbG9yLCBwb3IgbG8gcXVlIGRlYmVyw61hbW9zIHByb2NlZGVyIGEgZWxpbWluYXJsYSwgeWEgcXVlIG5vIG5vcyBwcm9wb3JjaW9uYSBpbmZvcm1hY2nDs246CgpgYGB7cn0KZmluYWxfZGF0YSA8LSBkcm9wX2NvbHVtbnMoZmluYWxfZGF0YSwgYygiZHN0X29yaWdpbiIsICJ0em9uZV9vcmlnaW4iLCAieWVhcl9mbGlnaHRzIiwgInR6X29yaWdpbiIpKQpgYGAKCkNvbiBmcmVjdWVuY2lhLCBlcyBtdXkgYmVuZWZpY2lvc28gb2JzZXJ2YXIgbGEgZGlzdHJpYnVjacOzbiBkZSBmcmVjdWVuY2lhIGJpdmFyaWFkYS4gUG9yIGVqZW1wbG8sIHBhcmEgdmVyIGNhcmFjdGVyw61zdGljYXMgZGlzY3JldGFzIHBvciBgYXJyX2RlbGF5YDoKCmBgYHtyfQpwbG90X2JhcihmaW5hbF9kYXRhLCB3aXRoID0gImFycl9kZWxheSIpCmBgYAoKTsOzdGVzZSBxdWUgbGEgZGlzdHJpYnVjacOzbiByZXN1bHRhbnRlIHNlIHZlIGJhc3RhbnRlIGRpZmVyZW50ZSBkZSBsYSBkaXN0cmlidWNpw7NuIGRlIGZyZWN1ZW5jaWFzIHJlZ3VsYXIuCgpQdWVkZSBvcHRhciBwb3IgZGl2aWRpciBwb3IgY29sb3JlcyB0b2RhcyBsYXMgZnJlY3VlbmNpYXMgcG9yIHVuYSB2YXJpYWJsZSBkaXNjcmV0YSwgY29tbyBwb3IgZWplbXBsbyBgb3JpZ2luYDoKCmBgYHtyfQpwbG90X2JhcihmaW5hbF9kYXRhLCBieSA9ICJvcmlnaW4iKQpgYGAKCiMjIyMgSGlzdG9ncmFtYXMKClBhcmEgdmlzdWFsaXphciBkaXN0cmlidWNpb25lcyBkZSB0b2RhcyBsYXMgdmFyaWFibGVzIGNvbnRpbnVhcyBwb2RlbW9zIHV0aWxpemFyIGxhIGZ1bmNpw7NuICBgcGxvdF9oaXN0b2dyYW0oKWA6CgpgYGB7cn0KcGxvdF9oaXN0b2dyYW0oZmluYWxfZGF0YSkKYGBgCgpUYW1iacOpbiBwb2RlbW9zIG9ic2VydmFyIHF1ZSBoYXkgdmFyaWFibGVzIGRlIGZlY2hhcyB5IGhvcmFzIHF1ZSBkZWJlbiB0cmF0YXJzZSB1biBwb2NvIG3DoXMsIHBvciBlamVtcGxvLCBjb25jZW50cmFuZG8gYcOxbywgbWVzLCBkw61hIHBhcmEgZm9ybWFyIHVuYSB2YXJpYWJsZSBkZSBgZmVjaGFgIHkvbyBhZ3JlZ2FyIGhvcmEgeSBtaW51dG8gcGFyYSBmb3JtYXIgbGEgdmFyaWFibGUgYGZlY2hhX2hvcmFgLgoKT3RyYSBwYXJ0ZSBxdWUgcG9kZW1vcyBsaW1waWFyLCBlcyBwb3IgZWplbXBsbyBsYSB2YXJpYWJsZSBgZmxpZ2h0YCwgeWEgcXVlIGVzYSBkZWJlcsOtYW1vcyB0ZW5lcmxhIGNvbW8gdW4gZmFjdG9yLCBwb3Igc2VyIHVuIG7Dum1lcm8gZGUgdnVlbG8geSBudW3DqXJpY2FtZW50ZSBubyB0aWVuZSBuaW5nw7puIHNpZ25pZmljYWRvOgoKYGBge3J9CmZpbmFsX2RhdGEgPC0gdXBkYXRlX2NvbHVtbnMoZmluYWxfZGF0YSwgImZsaWdodCIsIGFzLmZhY3RvcikKYGBgCgpUYW1iacOpbiBwb2RlbW9zIHJlbW92ZXIgbGFzIHZhcmlhYmxlcyBgIHllYXJfZmxpZ2h0c2AgeSBgIHR6X29yaWdpbmAgeWEgcXVlIHPDs2xvIGNvbnRpZW5lbiB1biB2YWxvcjoKCmBgYHtyfQpmaW5hbF9kYXRhIDwtIGRyb3BfY29sdW1ucyhmaW5hbF9kYXRhLCBjKCJ5ZWFyX2ZsaWdodHMiLCAidHpfb3JpZ2luIikpCmBgYAoKIyMjIyBRUSBwbG90IAoKTGEgZ3LDoWZpY2EgKlF1YW50aWxlLVF1YW50aWxlKiBlcyB1bmEgZm9ybWEgZGUgdmlzdWFsaXphciBsYSBkZXN2aWFzacOzbiBkZSB1bmEgZGlzdHJpYnVjacOzbiBkZSBwcm9iYWJpbGlkYWQgZXNwZWPDrWZpY2EuICAKCkRlc3B1w6lzIGRlIGFuYWxpemFyIGVzdG9zIGdyw6FmaWNvcywgYSBtZW51ZG8gZXMgYmVuZWZpY2lvc28gYXBsaWNhciB1bmEgdHJhbnNmb3JtYWNpw7NuIG1hdGVtw6F0aWNhIChjb21vIGxvZ2FyaXRtbykgcGFyYSBtb2RlbG9zIGNvbW8gbGEgcmVncmVzacOzbiBsaW5lYWwuIFBhcmEgaGFjZXJsbywgcG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIGBwbG90X3FxYC4gRGUgZm9ybWEgcHJlZGV0ZXJtaW5hZGEsIHNlIGNvbXBhcmEgY29uIGxhIGRpc3RyaWJ1Y2nDs24gbm9ybWFsLgoKTm90YTogTGEgZnVuY2nDs24gbGxldmFyw6EgbXVjaG8gdGllbXBvIGNvbiBtdWNoYXMgb2JzZXJ2YWNpb25lcywgcG9yIGxvIHF1ZSBwdWVkZSBvcHRhciBwb3IgZXNwZWNpZmljYXIgdW4gYHNhbXBsZWRfcm93c2AgYXByb3BpYWRvOgoKYGBge3J9CnFxX2RhdGEgPC0gZmluYWxfZGF0YVssIGMoImFycl9kZWxheSIsICJhaXJfdGltZSIsICJkaXN0YW5jZSIsICJzZWF0cyIpXQoKcGxvdF9xcShxcV9kYXRhLCBzYW1wbGVkX3Jvd3MgPSAxMDAwTCkKYGBgCgpFbiBlbCBncsOhZmljbywgYGFpcl90aW1lYCwgYGRpc3RhbmNlYCB5IGFzaWVudG9zIHBhcmVjZW4gc2VzZ2Fkb3MgZW4gYW1iYXMgY29sYXMuIEFwbGlxdWVtb3MgdW5hIHRyYW5zZm9ybWFjacOzbiBsb2dhcsOtdG1pY2Egc2ltcGxlIHkgZ3JhZmlxdWVtb3MgZGUgbnVldm8uCgpgYGB7cn0KbG9nX3FxX2RhdGEgPC0gdXBkYXRlX2NvbHVtbnMocXFfZGF0YSwgMjo0LCBmdW5jdGlvbih4KSBsb2coeCArIDEpKQoKcGxvdF9xcShsb2dfcXFfZGF0YVssIDI6NF0sIHNhbXBsZWRfcm93cyA9IDEwMDBMKQpgYGAKCkNvbiBlc3RvIG9idGVuZXIgdW5hIG1lam9yIGRpc3RyaWJ1Y2nDs24uIFNpIGVzIG5lY2VzYXJpbywgdGFtYmnDqW4gcHVlZGUgdmVyIGVsIGdyw6FmaWNvIFFRIG1lZGlhbnRlIG90cmEgZnVuY2nDs246CgpgYGB7cn0KcXFfZGF0YSA8LSBmaW5hbF9kYXRhWywgYygibmFtZV9vcmlnaW4iLCAiYXJyX2RlbGF5IiwgImFpcl90aW1lIiwgImRpc3RhbmNlIiwgInNlYXRzIildCgpwbG90X3FxKHFxX2RhdGEsIGJ5ID0gIm5hbWVfb3JpZ2luIiwgc2FtcGxlZF9yb3dzID0gMTAwMEwpCmBgYAoKIyMjIyBDb3JyZWxhdGlvbiBBbmFseXNpcwoKUGFyYSB2aXN1YWxpemFyIGVsIG1hcGEgZGUgY2Fsb3IgZGUgbGEgY29ycmVsYWNpw7NuIGRlIHRvZGFzIGxhcyB2YXJpYWJsZXMgcXVlIG5vIHRlbmdhbiBkYXRvcyBmYWx0YW50ZXMgbG8gcG9kZW1vcyByZWFsaXphciBkZSBsYSBzaWd1aWVudGUgZm9ybWE6CgpgYGB7cn0KcGxvdF9jb3JyZWxhdGlvbihuYS5vbWl0KGZpbmFsX2RhdGEpLCBtYXhjYXQgPSA1TCkKYGBgCgpUYW1iacOpbiBwdWVkZSBncmFmaWNhciB2YXJpYWJsZXMgc8OzbG8gZGlzY3JldGFzL2NvbnRpbnVhcyBkZSBsYSBzaWd1aWVudGUgZm9ybWE6CgpgYGB7cn0KcGxvdF9jb3JyZWxhdGlvbihuYS5vbWl0KGZpbmFsX2RhdGEpLCB0eXBlID0gImMiKQpwbG90X2NvcnJlbGF0aW9uKG5hLm9taXQoZmluYWxfZGF0YSksIHR5cGUgPSAiZCIpCmBgYAoKIyMjIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpCgpFbCBhbsOhbGlzaXMgZGUgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgKFBDQSwgcG9yIHN1cyBzaWdsYXMgZW4gaW5nbMOpcywpIGNvbnNpc3RlIGVuIGV4cHJlc2FyIHVuIGNvbmp1bnRvIGRlIHZhcmlhYmxlcyBlbiB1biBjb25qdW50byBkZSBjb21iaW5hY2lvbmVzIGxpbmVhbGVzIGRlIGZhY3RvcmVzIG5vIGNvcnJlbGFjaW9uYWRvcyBlbnRyZSBzw60sIGVzdG9zIGZhY3RvcmVzIGRhbmRvIGN1ZW50YSB1bmEgZnJhY2Npw7NuIGNhZGEgdmV6IG3DoXMgZMOpYmlsIGRlIGxhIHZhcmlhYmlsaWRhZCBkZSBsb3MgZGF0b3MuCgpFc3RlIGFuw6FsaXNpcyBsbyBwb2RlbW9zIHJlYWxpemFyIGRpcmVjdGFtZW50ZSBjb24gbGEgZnVuY2nDs24gYHBsb3RfcHJjb21wIChuYS5vbWl0IChmaW5hbF9kYXRhKSlgLCBwZXJvIFBDQSBmdW5jaW9uYXLDoSBtZWpvciBzaSBsaW1waWFtb3MgbG9zIGRhdG9zIHByaW1lcm86CgpgYGB7cn0KcGNhX2RmIDwtIG5hLm9taXQoZmluYWxfZGF0YVssIGMoIm9yaWdpbiIsICJkZXBfZGVsYXkiLCAiYXJyX2RlbGF5IiwgImFpcl90aW1lIiwgInllYXJfcGxhbmVzIiwgInNlYXRzIildKQoKcGxvdF9wcmNvbXAocGNhX2RmLCB2YXJpYW5jZV9jYXAgPSAwLjksIG5yb3cgPSAyTCwgbmNvbCA9IDJMKQpgYGAKCiMjIyMgQm94cGxvdHMKClN1cG9uZ2EgcXVlIGxlIGd1c3RhcsOtYSBjb25zdHJ1aXIgdW4gbW9kZWxvIHBhcmEgcHJlZGVjaXIgbG9zIHJldHJhc29zIGVuIGxhcyBsbGVnYWRhcywgcHVlZGUgdmlzdWFsaXphciBsYSBkaXN0cmlidWNpw7NuIGRlIHRvZGFzIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGNvbnRpbnVhcyBlbiBmdW5jacOzbiBkZSBsb3MgcmV0cmFzb3MgZW4gbGFzIGxsZWdhZGFzIGNvbiB1biBkaWFncmFtYSBkZSBjYWphL2JpZ290ZXMgKGJveHBsb3QpOgoKYGBge3J9CiMjIFJlZHVjZSBkYXRhIHNpemUgZm9yIGRlbW8gcHVycG9zZQphcnJfZGVsYXlfZGYgPC0gZmluYWxfZGF0YVssIGMoImFycl9kZWxheSIsICJtb250aCIsICJkYXkiLCAiaG91ciIsICJtaW51dGUiLCAiZGVwX2RlbGF5IiwgImRpc3RhbmNlIiwgInllYXJfcGxhbmVzIiwgInNlYXRzIildCgojIyBDYWxsIGJveHBsb3QgZnVuY3Rpb24KcGxvdF9ib3hwbG90KGFycl9kZWxheV9kZiwgYnkgPSAiYXJyX2RlbGF5IikKYGBgCgpFbnRyZSB0b2RvcyBsb3MgY2FtYmlvcyBzdXRpbGVzIGVuIGNvcnJlbGFjacOzbiBjb24gbG9zIHJldHJhc29zIGVuIGxhcyBsbGVnYWRhcywgc2UgcHVlZGUgZGV0ZWN0YXIgaW5tZWRpYXRhbWVudGUgcXVlIGxvcyBhdmlvbmVzIGNvbiBtw6FzIGRlIDMwMCBhc2llbnRvcyB0aWVuZGVuIGEgdGVuZXIgcmV0cmFzb3MgbXVjaG8gbcOhcyBsYXJnb3MgKDE2IH4gMjEgaG9yYXMpLiAqQWhvcmEgcG9kZW1vcyBwcm9mdW5kaXphciBtw6FzIHBhcmEgdmVyaWZpY2FyIG8gZ2VuZXJhciBtw6FzIGhpcMOzdGVzaXMuKgoKIyMjIyBTY2F0dGVycGxvdHMKClBhcmEgbWlyYXIgbGFzIHJlbGFjaW9uZXMgZW50cmUgdmFyaWFibGVzIHBvZGVtb3MgdmlzdWFsaXphciBzY2F0dGVycGxvdHMgbyBkaWFncmFtYXMgZGUgZGlzcGVyc2nDs24uCgpgYGB7cn0KYXJyX2RlbGF5X2RmMiA8LSBmaW5hbF9kYXRhWywgYygiYXJyX2RlbGF5IiwgImRlcF90aW1lIiwgImRlcF9kZWxheSIsICJhcnJfdGltZSIsICJhaXJfdGltZSIsICJkaXN0YW5jZSIsICJ5ZWFyX3BsYW5lcyIsICJzZWF0cyIpXQoKcGxvdF9zY2F0dGVycGxvdChhcnJfZGVsYXlfZGYyLCBieSA9ICJhcnJfZGVsYXkiLCBzYW1wbGVkX3Jvd3MgPSAxMDAwTCkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAgUGFyYSBjb211bmljYXIgbG9zIHJlc3VsdGFkb3MKCkVsIMO6bHRpbW8gcGFzbyBkZWwgZmx1am8gZXMgY29tdW5pY2FyIGxvcyByZXN1bHRhZG9zLiAgRXN0ZSBwdWVkZSB0ZW5lciBkaWZlcmVudGVzIGNhbWlub3MsIGRlcGVuZGllbmRvIGRlbCBww7pibGljby4KCjxjZW50ZXI+CiFbXSguL2ltYWdlcy9zaGlueS5wbmcpe3dpZHRoPTE1JX0KPC9jZW50ZXI+CgoqIFNpIGRlc2VhIHByZXNlbnRhciBsb3MgcmVzdWx0YWRvcyBhIHN1IGVtcHJlc2EgeSBwcm9kdWNpciB1bmEgaGVycmFtaWVudGEgcmVwcm9kdWNpYmxlIHF1ZSBwdWVkYSB1dGlsaXphcnNlIGVuIGxhIHByb2R1Y2Npw7NuLCBwdWVkZSBoYWNlciB1biBbYFNoaW55YF0oaHR0cHM6Ly9zaGlueS5yc3R1ZGlvLmNvbS8pIGFwcC4KCjxjZW50ZXI+CiFbXSguL2ltYWdlcy9ybWFya2Rvd24ucG5nKXt3aWR0aD0xNSV9CjwvY2VudGVyPgoKRG9jdW1lbnRvcyBkZSBbYFIgTWFya2Rvd25gXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS8pIChjb21vIGVzdGUgZG9jdW1lbnRvKSBzb24gZm9ybWFzIGRlIHJlYWxpemFyIGluZm9ybWVzLCBkb25kZSBzZSBwdWVkZSBjb21iaW5hciBjw7NkaWdvLCB2aXN1YWxpemFjaW9uZXMgeSBkZXNjcmlwY2lvbmVzICh0ZXh0bykuIFNlIHB1ZWRlIHJlYWxpemFyIGRvY3VtZW50b3MsIHByZXNlbnRhY2lvbmVzLCBodG1scywgZW50cmUgb3Ryb3MuIEVzdGUgdGllbmUgbGEgdmVudGFqYSBxdWUgcG9kZW1vcyBlc2NyaWJpciBlY3VhY2lvbmVzIGNvbW8gZW4gJFxMYVRlWCQsIGNvbW8gcG9yIGVqZW1wbG86CgokJApcaW50X3swfV57XGluZnR5fSBlXnstcyBcY2RvdCB0fSBmKHQpIGQgdD1cbGltIF97aCBccmlnaHRhcnJvdyBcaW5mdHl9IFxpbnRfezB9XntofSBlXnstcywgdH0gZih0KSBkIHQKJCQKCiogU2kgZGVzZWEgcHVibGljYXIgc3VzIGhhbGxhemdvcyBwYXJhIGxhIGNvbXVuaWRhZCBjaWVudMOtZmljYSwgcHVlZGUgZXNjcmliaXIgdW4gYXJ0w61jdWxvLgoKKiBUYW1iacOpbiBwdWVkZSBwdWJsaWNhcmxvIGVuIGzDrW5lYSBwYXJhIHF1ZSBjdWFscXVpZXJhIHB1ZWRhIGFjY2VkZXIgYSDDqWwuCgojIFByb2dyYW1hcgoKTGEgaWRlYSBjb24gZXN0YSBzZXNpw7NuIGVzIHF1ZSBhcHJlbmRlcywgcmVjdWVyZGVzIG8gbWVqb3JlcyB0dXMgaGFiaWxpZGFkZXMgZGUgcHJvZ3JhbWFjacOzbiBlbiAqUiouIExhIHByb2dyYW1hY2nDs24gZXMgdW5hIGhhYmlsaWRhZCB0cmFuc3ZlcnNhbCBwYXJhIHRvZG8gKmNpZW50w61maWNvIGRlIGRhdG9zKi4gCgpFbCAqY8OzZGlnbyogdGFtYmnDqW4gbG8gcG9kZW1vcyB2ZXIgY29tbyB1bmEgaGVycmFtaWVudGEgZGUgY29tdW5pY2FjacOzbiBjb24gb3RyYXMgcGVyc29uYXMsIGFkaWNpb25hbG1lbnRlIHF1ZSBlcyBsYSBmb3JtYSBkZSBkZWNpcmxlIGEgdHUgY29tcHV0YWRvcmEgcXVlIGRlYmUgZGUgaGFjZXIuIFBlbnNhciBlbiBjw7NkaWdvIGNvbW8gdW4gbWVkaW8gZGUgY29tdW5pY2FjacOzbiBub3MgcHVlZGUgYXl1ZGFyIGEgdHJhYmFqYXIgbWVqb3IgZW4gcHJveWVjdG9zIGNvbGFib3JhdGl2b3MuIAoKSW5jbHVzbyBzaSBubyB0cmFiYWphcyBjb24gcGVyc29uYXMsIHB1ZWRlIHF1ZSB0dSBtaXNtbyBuZWNlc2l0ZXMgdHUgY8OzZGlnbyBoYWNpYSBlbCBmdXR1cm8geSBlcyBtZWpvciBjb25zdHJ1aXIgdHUgY8OzZGlnbyBkZSB0YWwgZm9ybWEgcXVlLCBzaSBvdHJhIHBlcnNvbmEsIG8gdHUgZW4gZWwgZnV0dXJvIGxvIHJlcXVpZXJhcyB2b2x2ZXIgYSB2ZXIsIHNlYSBmw6FjaWwgeSByw6FwaWRvIGRlIGVudGVuZGVyLiAKCioqU2kgZXMgbGEgcHJpbWVyYSB2ZXogcXVlIHVzYXMgUioqIHRlIHJlY29taWVuZG8gcXVlIGVzdHVkaWVzIHVuIHBvY28gbcOhcyBkZSBsbyBhZGljaW9uYWwgYSBsYXMgY2xhc2VzLCBlbCBsaWJybyBbKkFkdmFuY2VkIFIgYnkgSGFkbGV5IFdpY2toYW0sICpdKGh0dHA6Ly9hZHYtci5oYWQuY28ubnovKSB0ZSBwdWVkZSBheXVkYXIgeSBkYXIgbWF5b3JlcyBpZGVhcyBkZSBjw7NtbyBwcm9ncmFtYXIgZW4gKlIqLgoKIyMgUGlwZXMKCkxvcyAqcGlwZXMqIHNvbiB1bmEgaGVycmFtaWVudGEgcGFyYSBlc2NyaWJpciBjw7NkaWdvIHNlY3VlbmNpYWwgZGUgbcO6bHRpcGxlcyBvcGVyYWNpb25lcy4gIEVsIHBpcGUgYCU+JWAgcHJvdmllbmUgZGVsIHBhcXVldGUgYG1hZ3JpdHRyYCwgcGVybyBlbCBwYXF1ZXRlIGRlIGB0aWR5dmVyc2VgIGNhcmdhbiBlbCBgJT4lYCBkZSBmb3JtYSBhdXRvbcOhdGljYSwgcG9yIGxvIHF1ZSBubyBlcyBuZWNlc2FyaW8gcXVlIHRlbmdhbW9zIGVsIHBhcXVldGUgYG1hZ3JpdHRyYCBlc3BlY2lmaWNhZG8gZXhwbMOtY2l0YW1lbnRlLiAgCgo8Y2VudGVyPgohW10oLi9pbWFnZXMvcGlwZS5wbmcpe3dpZHRoPTE1JX0KPC9jZW50ZXI+CgpFbCBvYmpldGl2byBkZSBsb3MgKnBpcGVzKiBlcyBheXVkYXJ0ZSBkZSBlc2NyaWJpciBjw7NkaWdvIGRlIHRhbCBmb3JtYSBxdWUgc2VhIG3DoXMgZsOhY2lsIGRlIGxlZXIgeSBkZSBlbnRlbmRlci4gCgpVbiBlamVtcGxvIGVzIHRyYW5zZm9ybWFyIHVuIGRhdGFmcmFtZSBwb3IgbWVkaW8gZGUgZmlsdHJvcywgYWdydXBhY2lvbmVzLCBldGMuICBFc3RhIGZvcm1hIGRlIHRyYW5zZm9ybWFyIGxvcyBkYXRvcyBsbyBoYWNlbW9zIGNvbiBsYSBheXVkYSBkZWwgcGFxdWV0ZSBgZHBseXJgIHF1ZSB5YSB2aWVuZSBkZW50cm8gZGUgYHRpZHl2ZXJzZWAuIFBhcmEgdmVyIHRvZGFzIGxhcyBmdW5jaW9uZXMgcXVlIHRpZW5lIGVzdGUgcGFxdWV0ZSBwYXJhIHRyYW5zZm9ybWFjacOzbiBkZSBkYXRvcyBbYXF1w60gdGUgZGVqbyB1biBsaW5rXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwpIHF1ZSB0ZSBwdWVkZSBheXVkYXIuCgpgYGB7cn0KIyBkYXRhZnJhbWUgb3JpZ2luYWwKaGVhZChtcGcpCgojIGRhdGEgZnJhbWUgdHJhbnNmb3JtYWRvIGNvbiBwaXBlCm1wZyAlPiUgICAgZmlsdGVyKG1hbnVmYWN0dXJlciA9PSAnYXVkaScpICU+JSAKICBncm91cF9ieSh5ZWFyKSAlPiUgIHN1bW1hcmlzZShjdHlfbWVhbiA9IG1lYW4oY3R5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBod3lfbWVhbiA9IG1lYW4oaHd5KSkgCmBgYAoKIyMgRnVuY2lvbmVzCgpMYXMgZnVuY2lvbmVzIG5vcyBwZXJtaXRlbiBhdXRvbWF0aXphciB0YXJlYXMgY29tdW5lcyBkZSBtYW5lcmEgbcOhcyBwb3RlbnRlIHkgZ2VuZXJhbCBxdWUgY29waWFyIHkgcGVnYXIuIEVzY3JpYmlyIHVuYSBmdW5jacOzbiB0aWVuZSB0cmVzIHZlbnRhamFzIHByaW5jaXBhbGVzOgoKMS4gUHVlZGUgZGFyIGEgdW5hIGZ1bmNpw7NuIHVuIG5vbWJyZSBxdWUgdGUgZGUgaWRlYSBkZSBxdcOpIGhhY2UgZWwgY8OzZGlnby4KCjIuIEN1YW5kbyBzZSByZXF1aWVyYSBjYW1iaWFyLCBzw7NsbyBzZSBjYW1iaWEgdW5hIHZleiwgZW4gbHVnYXIgZGUgcXVlIHNpIHRlbmVtb3MgZWwgbWlzbW8gY8OzZGlnbyByZXBldGlkbywgdGVuZW1vcyBxdWUgY2FtYmlhcmxvIGxhcyB2ZWNlcyBxdWUgbG8gdGVuZ2Ftb3MgcmVwZXRpZG8uCgozLiBTZSBlbGltaW5hIGxhIHBvc2liaWxpZGFkIGRlIGNvbWV0ZXIgZXJyb3Jlcy4KCkRlYmVyw61hcyBjb25zaWRlcmFyIGVzY3JpYmlyIHVuYSBmdW5jacOzbiBzaWVtcHJlIHF1ZSBoYXlhcyBjb3BpYWRvIHkgcGVnYWRvIHVuIGNsb3F1ZSBkZSBjw7NkaWdvIG3DoXMgZGUgZG9zIHZlY2VzLiBWZWFtb3MgdW4gZWplbXBsbzoKCmBgYHtyfQpkZiA8LSB0aWJibGU6OnRpYmJsZSgKICBhID0gcm5vcm0oMTApLAogIGIgPSBybm9ybSgxMCksCiAgYyA9IHJub3JtKDEwKSwKICBkID0gcm5vcm0oMTApCikKCmRmJGEgPC0gKGRmJGEgLSBtaW4oZGYkYSwgbmEucm0gPSBUUlVFKSkgLyAKICAobWF4KGRmJGEsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGYkYSwgbmEucm0gPSBUUlVFKSkKZGYkYiA8LSAoZGYkYiAtIG1pbihkZiRiLCBuYS5ybSA9IFRSVUUpKSAvIAogIChtYXgoZGYkYiwgbmEucm0gPSBUUlVFKSAtIG1pbihkZiRhLCBuYS5ybSA9IFRSVUUpKQpkZiRjIDwtIChkZiRjIC0gbWluKGRmJGMsIG5hLnJtID0gVFJVRSkpIC8gCiAgKG1heChkZiRjLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRmJGMsIG5hLnJtID0gVFJVRSkpCmRmJGQgPC0gKGRmJGQgLSBtaW4oZGYkZCwgbmEucm0gPSBUUlVFKSkgLyAKICAobWF4KGRmJGQsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGYkZCwgbmEucm0gPSBUUlVFKSkKYGBgCgpFc3RlIGPDs2RpZ28gbG8gcXVlIGhhY2UgZXMgcXVlIHJlZXNjYWxhIGNhZGEgY29sdW1uYSBkZWwgZGF0YWZyYW1lIGBkZmAgcGFyYSBxdWUgdGVuZ2EgdW4gdmFsb3IgZW50cmUgMCB5IDEuIFBlcm8sIGhheSB1biBlcnJvciBlbiBlbCBjw7NkaWdvIGN1YW5kbyBzZSBjb3Bpw7MgeSBwZWfDsyBlbiBgZGYkYmAsIGVuIGVsIG1vbWVudG8gZGUgY29waWFyIHkgcGVnYXIgaGF5IHVuYSBgYWAgZW4gdW5hIHBhcnRlIGRlbCBjw7NkaWdvIGVuIGx1Z2FyIGRlIHVuYSBgYmAuICrCv0xvIHZpc3RlPyouCgpQYXJhIGVzY3JpYmlyIHVuYSBmdW5jacOzbiBoYXkgcXVlIGFuYWxpemFyIHByaW1lcm8gZWwgY8OzZGlnby4gwr9DdcOhbnRhcyBlbnRyYWRhcyB0aWVuZSw/IHZlYW1vcyBsYSBwYXJ0ZSBxdWUgc2UgcmVwaXRlOgoKYGBge3J9CihkZiRhIC0gbWluKGRmJGEsIG5hLnJtID0gVFJVRSkpIC8KICAobWF4KGRmJGEsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGYkYSwgbmEucm0gPSBUUlVFKSkKYGBgCgpFc3RlIGPDs2RpZ28sIGxhIHBhcnRlIHF1ZSB2YW1vcyBhIGNhbWJpYXIgZXMgYGRmJGFgLCBlcyBkZWNpciBzw7NsbyB0aWVuZSB1bmEgdmFyaWFibGUgZGUgZW50cmFkYSBzaSBsbyB2ZW1vcyBjb21vIHVuYSBmdW5jacOzbi4gTG8gcXVlIGVzdGFtb3MgaGFjaWVuZG8gZW4gZWwgY8OzZGlnbyBjb24gZWwgbcOheGltbyB5IGVsIG3DrW5pbW8gZW4gcmVhbGlkYWQgZXMgbWlyYXIgc3UgcmFuZ28sIHBvZGVtb3MgdXRpbGl6YXIgZnVuY2nDs24gYHJhbmdlKClgLCBwYXJhIHN1IGPDoWxjdWxvIGRlIHRhbCBmb3JtYSBxdWUsIGxhIHByaW1lcmEgZW50cmFkYSBlcyBlbCBtw61uaW1vIHkgbGEgc2VndW5kYSBlbCBtw6F4aW1vLCBhc8OtLCBsYSBmdW5jacOzbiBxdWUgY3JlYXJlbW9zIHNlcsOhOgoKYGBge3J9CiMgRnVuY2nDs24KcmVzY2FsZTAxIDwtIGZ1bmN0aW9uKHgpIHsKICBybmcgPC0gcmFuZ2UoeCwgbmEucm0gPSBUUlVFKQogICh4IC0gcm5nWzFdKSAvIChybmdbMl0gLSBybmdbMV0pCn0KCiNFdmFsdWFjacOzbiBkZSBsYSBmdW5jacOzbgpyZXNjYWxlMDEoYygwLCA1LCAxMCkpCmBgYAoKSGF5IHRyZXMgcGFzb3MgY2xhdmVzIHBhcmEgY3JlYXIgdW5hIGZ1bmNpw7NuOgoKMS4gRWxlZ2lyIGNvcnJlY3RhbWVudGUgZWwgbm9tYnJlIGRlIGxhIGZ1bmNpw7NuLiAKCjIuIEVudW1lcmFyIGxhcyBlbnRyYWRhcyBvIGFyZ3VtZW50b3MgZGUgbGEgZnVuY2nDs24uCgozLiBRdWUgdHUgY8OzZGlnbyBkZW50cm8gZGUgbGEgZnVuY2nDs24gZGVwZW5kYSBkZSBsYXMgdmFyaWFibGVzIGRlIGVudHJhZGEuIAoKQ29uIGVzdGEgZnVuY2nDs24sIHNlIHJlc3VlbHZlIGVsIHByb2JsZW1hIG9yaWdpbmFsIGRvbmRlIHRlbsOtYW1vcyBlbCBlcnJvciBub3MgcXVlZGFyw61hIGRlIGxhIHNpZ3VpZW50ZSBmb3JtYToKCmBgYHtyfQpkZiRhIDwtIHJlc2NhbGUwMShkZiRhKQpkZiRiIDwtIHJlc2NhbGUwMShkZiRiKQpkZiRjIDwtIHJlc2NhbGUwMShkZiRjKQpkZiRkIDwtIHJlc2NhbGUwMShkZiRkKQpgYGAKCiMjIEl0ZXJhY2lvbmVzCgpVbmEgZGUgbGFzIGhlcnJhbWllbnRhcyBwYXJhIGF5dWRhcm5vcyBhIGV2aXRhciBkdXBsaWNhciBjw7NkaWdvIHNvbiBsYXMgKml0ZXJhY2lvbmVzKiwgYXVucXVlIGRlYmVtb3MgZGUgdGVuZXIgY3VpZGFkbyBjb24gZWxsYXMgeSBubyBkYXJsZXMgdW4gbWFsIHVzby4KClZvbHZpZW5kbyBhbCBlamVtcGxvIGFudGVyaW9yCgpgYGB7cn0KZGYgPC0gdGliYmxlKAogIGEgPSBybm9ybSgxMCksCiAgYiA9IHJub3JtKDEwKSwKICBjID0gcm5vcm0oMTApLAogIGQgPSBybm9ybSgxMCkKKQpgYGAKCnF1ZXJlbW9zIGNhbGN1bGFyIGxhIG1lZGlhIHBvciBjb2x1bW5hLCBwb2Ryw61hbW9zIGNvcGlhciB5IHBlZ2FyIHJlYWxpemFybG86CmBgYHtyfQptZWRpYW4oZGYkYSkKbWVkaWFuKGRmJGIpCm1lZGlhbihkZiRjKQptZWRpYW4oZGYkZCkKYGBgCgpVbmEgYWx0ZXJuYXRpdmEgcGFyYSBubyBjb3BpYXIgeSBwZWdhciBlcyBoYWNlciB1biBidWNsZToKYGBge3J9Cm91dHB1dCA8LSB2ZWN0b3IoImRvdWJsZSIsIG5jb2woZGYpKSAgIyAxLiBvdXRwdXQKZm9yIChpIGluIHNlcV9hbG9uZyhkZikpIHsgICAgICAgICAgICAjIDIuIHNlcXVlbmNlCiAgb3V0cHV0W1tpXV0gPC0gbWVkaWFuKGRmW1tpXV0pICAgICAgIyAzLiBib2R5Cn0Kb3V0cHV0CmBgYAoKUGFyIGxvcyBidWNsZXMgbyAqbG9vcHMqIHRlbmVtb3MgdHJlcyBjb21wb25lbnRlOgoKMS4gTGEgc2FsaWRhIGBvdXRwdXQgPC0gdmVjdG9yKCJkb3VibGUiLCBsZW5ndGgoeCkpYCAKCjIuIG5hIGZvcm1hIGdlbmVyYWwgZGUgY3JlYXIgdW4gdmVjdG9yIHZhY8OtbyBkZSB1bmEgbG9uZ2l0dWQgZGV0ZXJtaW5hZGEgZXMgbGEgZnVuY2nDs24gYHZlY3RvcigpYAoKMy4gTGEgc2VjdWVuY2lhOiBpIGVuIGBzZXFfYWxvbmcoZGYpYC4gRXN0byBkZXRlcm1pbmEgbG8gcXVlIHNlIHZhIGEgcmVjb3JyZXIgZW4gZWwgYnVjbGU6IGNhZGEgZWplY3VjacOzbiBkZWwgYnVjbGUgYGZvcmAgYXNpZ25hcsOhIGEgYGlgIHVuIHZhbG9yIGRpZmVyZW50ZSBkZSBgc2VxX2Fsb25nKGRmKWAuIAoKIyBQcsOhY3RpY2EgTm8uIDEKCkV4cGxvcmFjacOzbiB5IGxpbXBpZXphIGRlIGRhdG9zIHBhcmEgbG9zIHNpZ3VpZW50ZXMgZGF0YWZyYW1lOgoKMS4gW2BDb2ZmZWUgcmF0aW5nc2BdKGh0dHBzOi8vZ2l0aHViLmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvYmxvYi9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDctMDcvcmVhZG1lLm1kKSBjb25qdW50byBkZSBkYXRvcyBxdWUgbWlkZSBsYSBjYWxpZmljYWNpw7NuIGRlbCBjYWbDqSBzZWfDum4gc3VzIGNhcmFjdGVyw61zdGljYXMuIFZhcmlhYmxlIHJlc3B1ZXN0YTogY3VwcGVyX3BvaW50cy4KCjIuIFtgR2VybWFuIENyZWRpdCBSaXN0a2BdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY29kZS9qYW5pb2JhY2htYW5uL2dlcm1hbi1jcmVkaXQtYW5hbHlzaXMtYS1yaXNrLXBlcnNwZWN0aXZlL2RhdGEpIHF1ZSBjb250aWVuZSB2YXJpYWJsZXMgZGUgY3LDqWRpdG8gcGFyYSByZWFsaXphciB1biBzY29yZSBkZSBwcm9iYWJpbGlkYWQgKHNpIGxhIHBlcnNvbmEgcGFnYSBvIG5vIHBhZ2EpIHNlZ8O6biBzdXMgY2FyYWN0ZXLDrXN0aWNhcy4gVmFyaWFibGUgcmVzcHVlc3RhOiBSaXNrLgoKUGFyYSBjYWRhIGNvbmp1bnRvIGRlIGRhdG9zIHZhbiBhIHJlYWxpemFyIHVuIGBSbWFya2Rvd25gLCBkb25kZSBzZSBwcmVzZW50ZSBsYSBleHBsb3JhY2nDs24geSBsaW1waWV6YSBkZSBsb3MgZGF0b3MuICBTaSB1c3RlZCBjb25zaWRlcmEgcXVlIHNlIGRlYmUgZGUgYWRpY2lvbmFyIHVuYSB2YXJpYWJsZSBvIG1vZGlmaWNhciBlbCBkYXRhc2V0IGNvbiBhZ3J1cGFjaW9uZXMgbyBmaWx0cm9zIHBvciBmYXZvciBlc3BlY2lmaWNhcmxvIGVuIGVsIHJlcG9ydGUuIAoKQW50ZXMgZGUgY29tZW56YXIgY29uIGVsIHJlcG9ydGUgaGFjZXJzZSB1bmEgcHJlZ3VudGEgc29icmUgbG9zIGRhdG9zIHkgcmVzcG9uZGVyIGEgZWxsYSBwb3IgbWVkaW8gZGUgdW5hIG8gbcOhcyB2aXN1YWxpemFjaW9uZXMuIEFsIGZpbmFsIGRlbCByZXBvcnRlIGNvbG9jYXIgc3VzIHByaW5jaXBhbGVzIGhhbGxhemdvcyB5IGNvbmNsdXNpb25lcy4gCgpOT1RBOiBObyBzZSB0cmF0YSBkZSBoYWNlciB2aXN1YWxpemFjaW9uZXMgcG9yIGhhY2VybGFzLCBjYWRhIHVuYSBkZWJlIGRlIGlyIGFjb21wYcOxYWRhIGRlIHVuIGFuw6FsaXNpcyByZXNwZWN0byBhIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBwYXJhIGNhZGEgbW9kZWxvLgoKCg==